# Documentação da Lacuna Software
> Documentação para desenvolvedores dos produtos de certificação digital, assinatura digital e PKI da Lacuna Software: Web PKI, Rest PKI Core, PKI SDK, PKI Express, Signer, Amplia e serviços relacionados.
Index: https://docs.lacunasoftware.com/pt-br/llms.txt
# [Bem-vindo ao Lacuna Docs](https://docs.lacunasoftware.com/articles/welcome)
Seja bem-vindo ao novo portal de documentação da [Lacuna Software](https://www.lacunasoftware.com/). Utilizando a mesma
tecnologia do portal [Microsoft Docs](https://docs.microsoft.com/), este site combina toda a informação técnica que
você pode precisar sobre cada um de nossos produtos em uma experiência unificada e interconectada de documentação.
> **Dica**
>
> Este site é destinado a desenvolvedores de software. Se você é um usuário final tendo problemas para utilizar algum
> de nossos produtos, como o plugin Web PKI, por favor visite a nossa [Central de Suporte](http://lacuna.help/)
Se você encontrar algum erro ou alguma documentação faltando, por favor
[crie uma issue](https://github.com/LacunaSoftware/docs/issues) reportando o problema.
Um bom lugar para começar é o artigo [Soluções para certificação digital](https://docs.lacunasoftware.com/articles/pki-guide).
## Por onde começar
- [Como escolher um produto](https://docs.lacunasoftware.com/articles/welcome/choosing-a-product): qual produto atende à sua integração, com link para a referência rápida de cada um.
- [Como usar esta documentação com agentes de IA](https://docs.lacunasoftware.com/articles/welcome/using-with-ai-agents): llms.txt, a versão em Markdown de cada página,
as especificações OpenAPI e um parágrafo para o seu agente de programação.
# [Como escolher um produto](https://docs.lacunasoftware.com/articles/welcome/choosing-a-product)
Comece pelo que a sua aplicação precisa fazer. Cada produto aponta para a sua referência rápida: instalação,
configuração, operações principais, erros e exemplos numa só página.
## Produtos principais
| Se a sua aplicação precisa… | Use |
|---|---|
| Trabalhar com os certificados do usuário no navegador: listá-los, lê-los, assinar com eles numa página web | [Web PKI](https://docs.lacunasoftware.com/articles/web-pki/quick-reference) |
| Assinar, validar e aplicar carimbo de tempo em documentos por uma API REST, como serviço em nuvem ou em instância própria | [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/quick-reference) |
| Assinar nos próprios servidores Windows ou Linux, numa linguagem que não seja .NET (Java, PHP, Python, Node.js, Ruby…) | [PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference) |
| Assinar, validar e tratar certificados dentro de uma aplicação .NET | [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/quick-reference) |
| Enviar documentos para pessoas e colher as assinaturas delas, com um fluxo | [Signer](https://docs.lacunasoftware.com/articles/signer/quick-reference) |
| Emitir certificados: operar as próprias autoridades certificadoras ou uma PKI inteira | [Amplia](https://docs.lacunasoftware.com/articles/amplia/quick-reference) |
Já integra o **Rest PKI** em pki.rest? Ele é um produto legado que continua recebendo correções de bugs e de segurança; veja
a [referência rápida dele (legado)](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference). Para uma nova integração em nuvem, use o Rest PKI Core.
## Combinações comuns
- **Assinatura numa página web com o certificado do usuário**: o Web PKI faz a parte que precisa rodar no
navegador, e um produto no servidor (Rest PKI Core, PKI SDK ou PKI Express) faz o restante. O artigo
[assinaturas web](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures) do guia de certificação digital explica as abordagens.
- **Assinatura com chave guardada no servidor**: sem etapa no navegador. Em .NET, o guia recomenda o PKI SDK; veja
[assinaturas no servidor](https://docs.lacunasoftware.com/articles/pki-guide/server-signatures).
- **Login com certificado**: o Web PKI no navegador e um produto no servidor para verificar o certificado; veja
[login com certificado digital](https://docs.lacunasoftware.com/articles/pki-guide/cert-auth).
## Outros produtos
| Produto | O que faz |
|---|---|
| [Amplia Reg](https://docs.lacunasoftware.com/articles/amplia-reg) | Autoridade de Registro do Amplia: confirma quem é o titular antes da emissão do certificado |
| [PSC](https://docs.lacunasoftware.com/articles/psc) | Armazena e gerencia certificados em nuvem, junto com o Amplia |
| [TSA](https://docs.lacunasoftware.com/articles/tsa) | Servidor de carimbo de tempo (RFC 3161) |
| [GrantID](https://docs.lacunasoftware.com/articles/grant-id) | Single Sign On e múltiplos fatores de autenticação para os seus aplicativos |
| [Bulk Signer](https://docs.lacunasoftware.com/articles/bulk-signer) | Serviço on-premises que assina arquivos em lote |
| [Scanner](https://docs.lacunasoftware.com/articles/scanner) | Adiciona digitalização de documentos a uma aplicação web |
| [Digiploma](https://docs.lacunasoftware.com/articles/digiploma) | Emite, registra e publica diplomas digitais conforme a Portaria MEC 554/2019 |
| [TrustBridge](https://docs.lacunasoftware.com/articles/trustbridge) | Assinatura remota com certificados ICP-Brasil de pessoas, em smart card ou token USB |
Para entregar esta documentação a um agente de IA de programação, veja
[Como usar esta documentação com agentes de IA](https://docs.lacunasoftware.com/articles/welcome/using-with-ai-agents).
# [Como usar esta documentação com agentes de IA](https://docs.lacunasoftware.com/articles/welcome/using-with-ai-agents)
Quando um agente de IA de programação (Claude Code, Cursor, GitHub Copilot, Codex…) escreve a sua integração,
indique a ele os recursos abaixo. Eles têm o mesmo conteúdo das páginas que você lê, em formatos que um agente
baixa e interpreta com facilidade. A [última seção](https://docs.lacunasoftware.com/articles/welcome/using-with-ai-agents#um-parágrafo-para-o-seu-agente) traz um parágrafo pronto para colar nas
instruções do seu agente.
## Comece pelo llms.txt
| Arquivo | O que contém |
|---|---|
| [llms.txt](https://docs.lacunasoftware.com/llms.txt) | Índice das páginas em inglês, agrupadas por produto, cada uma com a URL em Markdown e uma descrição de uma linha. A referência rápida de cada produto vem primeiro. As páginas que ainda não existem em inglês apontam para a versão em português, marcadas com (pt-br). Termina com as especificações OpenAPI. |
| [llms-full.txt](https://docs.lacunasoftware.com/llms-full.txt) | As mesmas páginas na íntegra, num só arquivo, cada uma sob um título com a sua URL. |
| [pt-br/llms.txt](https://docs.lacunasoftware.com/pt-br/llms.txt), [pt-br/llms-full.txt](https://docs.lacunasoftware.com/pt-br/llms-full.txt) | O mesmo para as páginas em português. |
Os dois seguem o formato [llms.txt](https://llmstxt.org/).
## Cada página em Markdown
Acrescente `.md` à URL de qualquer página para obtê-la em Markdown puro. Por exemplo,
https://docs.lacunasoftware.com/articles/signer/get-started.md é a versão em Markdown de
https://docs.lacunasoftware.com/articles/signer/get-started.
- O front matter traz o título, a descrição, a URL canônica, o idioma, o produto e a data da última alteração.
- As abas viram um bloco de código por aba, cada um com um rótulo em negrito; observações e avisos viram
citações; links e imagens são URLs absolutas.
- Cada página aponta para a sua versão em Markdown no head do HTML (` `)
e abaixo do conteúdo (**Ver em Markdown**, **Copiar página em Markdown**).
- A referência da API .NET do PKI SDK funciona do mesmo jeito: https://docs.lacunasoftware.com/api/Lacuna.Pki/PKCertificate.md.
## Referências rápidas
Uma página por produto com o que uma integração precisa primeiro: instalação, configuração e autenticação,
operações principais com código, erros, certificados de teste, demonstrações e exemplos. O que a documentação
ainda não cobre fica marcado como `TODO(docs)` em vez de ser deduzido.
- [Web PKI](https://docs.lacunasoftware.com/articles/web-pki/quick-reference)
- [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/quick-reference)
- [PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference)
- [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/quick-reference)
- [Signer](https://docs.lacunasoftware.com/articles/signer/quick-reference)
- [Amplia](https://docs.lacunasoftware.com/articles/amplia/quick-reference)
- [Rest PKI (legado)](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference)
Em dúvida sobre qual produto usar? Veja [Como escolher um produto](https://docs.lacunasoftware.com/articles/welcome/choosing-a-product).
## Especificações das APIs
A especificação OpenAPI de cada API em nuvem é copiada para uma URL estável neste site. As instâncias on-premises
servem a sua própria em `/swagger`.
| API | Especificação | Especificação ao vivo |
|---|---|---|
| Rest PKI Core | [openapi/rest-pki-core.json](https://docs.lacunasoftware.com/openapi/rest-pki-core.json) | https://core.pki.rest/swagger/api/swagger.json |
| Signer | [openapi/signer.json](https://docs.lacunasoftware.com/openapi/signer.json) | https://www.dropsigner.com/swagger/api/swagger.json |
| Amplia | [openapi/amplia.json](https://docs.lacunasoftware.com/openapi/amplia.json) | https://amplia.lacunasoftware.com/swagger/api/swagger.json |
| Amplia Reg | [openapi/amplia-reg.json](https://docs.lacunasoftware.com/openapi/amplia-reg.json) | https://ar.lacunasoftware.com/swagger/api/swagger.json |
| CloudHub | [openapi/cloudhub.json](https://docs.lacunasoftware.com/openapi/cloudhub.json) | https://cloudhub.lacunasoftware.com/swagger/v1/swagger.json |
| Digiploma | [openapi/digiploma.json](https://docs.lacunasoftware.com/openapi/digiploma.json) | https://www.digiploma.com.br/swagger/api/swagger.json |
| Scanner | [openapi/scanner.json](https://docs.lacunasoftware.com/openapi/scanner.json) | https://scn.lacunasoftware.com/swagger/api/swagger.json |
| Rest PKI (legado) | [openapi/rest-pki.json](https://docs.lacunasoftware.com/openapi/rest-pki.json) | https://pki.rest/swagger/docs/v1 |
## Demonstrações e exemplos
- **Demonstrações**: https://try.lacunasoftware.com/demos.json lista as demonstrações de
[try.lacunasoftware.com](https://try.lacunasoftware.com/pt), com o produto, a página da documentação e os
exemplos de cada uma.
- **Exemplos**: os repositórios [PkiSuiteSamples](https://github.com/LacunaSoftware/PkiSuiteSamples) e
[SignerSamples](https://github.com/LacunaSoftware/SignerSamples) mantêm cada um um catálogo `samples.json`: uma
entrada por tarefa e linguagem, com o arquivo a ler, como executá-lo, a página da documentação e a demonstração.
- As páginas que tratam de uma tarefa terminam com um quadro **Demonstrações e exemplos** que aponta para a
demonstração e para o exemplo em cada linguagem.
## Um parágrafo para o seu agente
Cole isto nas instruções do seu agente (por exemplo `CLAUDE.md`, `AGENTS.md` ou `.cursor/rules`) e diga qual
produto você usa. O texto fica em inglês, o idioma mais completo da documentação:
```text
This project integrates Lacuna Software products (digital certificates, digital signatures, PKI).
Use the official docs at https://docs.lacunasoftware.com and read them as Markdown:
- Start with https://docs.lacunasoftware.com/llms.txt, the index of all pages by product.
- Add ".md" to any docs page URL to get its Markdown version.
- Read the product's quick reference first (listed first under the product in llms.txt): packages,
configuration, core operations, errors and samples.
- For REST APIs, use the OpenAPI specs at https://docs.lacunasoftware.com/openapi/ (listed at the
end of llms.txt).
- For working code, follow the samples linked from the docs pages (PkiSuiteSamples and
SignerSamples on GitHub) in this project's language.
Do not guess package names, versions, endpoints or headers: take them from the quick reference, the
OpenAPI spec or a sample. When developing, use the test certificates from
https://docs.lacunasoftware.com/en-us/articles/pki-guide/test-certs, never production certificates.
```
# [Licenciamento dos produtos Lacuna](https://docs.lacunasoftware.com/articles/welcome/licensing)
Esta página reúne, em um só lugar, o que é preciso para **adquirir, ativar, atualizar e usar** as licenças dos
produtos da Lacuna Software. O conteúdo foi compilado a partir da documentação de cada produto, que continua sendo a
referência detalhada: a seção de cada produto termina com os links para os artigos de origem.
> **Dica: Você é usuário final?**
>
> Se você apenas usa um site ou sistema que pede o Web PKI para assinar documentos ou fazer login com certificado
> digital, **não precisa comprar nem configurar nenhuma licença**. A licença é de quem desenvolve e mantém o site.
> Veja [Para usuários finais](https://docs.lacunasoftware.com/articles/welcome/licensing#usuario-final).
## Resumo por produto
| Produto e cenário | Precisa de licença? | O que é usado |
|---|---|---|
| [Web PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#web-pki) em aplicação no *localhost* (desenvolvimento) | Não | — |
| [Web PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#web-pki) com o Rest PKI em nuvem (`https://pki.rest`) | Não, inclusive em produção | — |
| [Web PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#web-pki) nos demais casos, fora do *localhost* | Sim | Licença do Web PKI, informada no JavaScript da página |
| [Rest PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki) em nuvem | Não usa arquivo de licença | Conta em `pki.rest` e token de acesso |
| [Rest PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki) *on-premises* | Sim | Licença do PKI SDK (arquivo `LacunaPkiLicense.txt`) |
| [Rest PKI Core](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-core) em nuvem (`core.pki.rest`) | Não usa arquivo de licença | Endpoint e chave de API, fornecidos pelo suporte |
| [Rest PKI Core](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-core) *on-premises* | Sim | Licença do PKI SDK (obrigatória) e licença do Web PKI (para assinatura no navegador) |
| [PKI SDK](https://docs.lacunasoftware.com/articles/welcome/licensing#pki-sdk) | Sim | Licença do PKI SDK (arquivo `LacunaPKILicense.config` ou texto em Base64) |
| [PKI Express](https://docs.lacunasoftware.com/articles/welcome/licensing#pki-express) | Sim | Arquivo `LacunaPkiLicense.config`, usado para ativar o PKI Express na máquina |
| [Signer](https://docs.lacunasoftware.com/articles/welcome/licensing#signer) *on-premises* | Sim | Licença do PKI SDK e licença do Web PKI |
| [Bulk Signer](https://docs.lacunasoftware.com/articles/welcome/licensing#bulk-signer) | Sim | Licença do PKI SDK (texto em Base64) |
| [Usuário final](https://docs.lacunasoftware.com/articles/welcome/licensing#usuario-final) de um site ou sistema | Não | — |
## Entendendo as licenças
Os produtos desta página usam uma ou mais destas licenças:
- **Licença do PKI SDK**: licença da biblioteca .NET da Lacuna. Ela é usada diretamente pelas aplicações que usam o
[PKI SDK](https://docs.lacunasoftware.com/articles/welcome/licensing#pki-sdk) e também é exigida na instalação *on-premises* do [Rest PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki), do
[Rest PKI Core](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-core), do [Signer](https://docs.lacunasoftware.com/articles/welcome/licensing#signer) e do [Bulk Signer](https://docs.lacunasoftware.com/articles/welcome/licensing#bulk-signer). É entregue como arquivo
(`LacunaPKILicense.config` ou, no Rest PKI *on-premises*, `LacunaPkiLicense.txt`) ou como texto em Base64. Uma
licença do PKI SDK pode ter uso restrito ao Rest PKI.
- **Licença do Web PKI**: autoriza o uso do componente de navegador nos domínios de um site. É informada pelo
desenvolvedor no código JavaScript da página e, nas instalações *on-premises* do Rest PKI Core e do Signer, também
na configuração do servidor.
- **Licença do PKI Express**: arquivo `LacunaPkiLicense.config`, usado para ativar o PKI Express na máquina onde ele
está instalado.
Os serviços em nuvem não usam arquivo de licença: o acesso é feito com credenciais da sua conta, como o **token de
acesso** do Rest PKI e a **chave de API** do Rest PKI Core. Essas credenciais não substituem a licença do Web PKI nos
cenários em que ela é necessária (veja [Quando a licença do Web PKI é necessária](https://docs.lacunasoftware.com/articles/welcome/licensing#web-pki-quando)).
> **Info: O que significa "atualizar a versão da licença"**
>
> Na prática, é uma destas situações:
>
> 1. **A licença venceu, vai vencer ou precisa cobrir algo novo**, como um novo domínio no Web PKI. Você recebe uma
> nova licença e troca o valor configurado.
> 2. **Você vai atualizar a versão de um produto *on-premises*.** A licença do PKI SDK aceita apenas versões do SDK
> lançadas até uma certa data. Por isso, antes de atualizar o Signer ou o Rest PKI Core, confira no histórico de
> versões se a sua licença cobre a versão de destino. Se não cobrir, peça uma licença atualizada antes de atualizar.
>
> O [passo a passo para atualizar uma licença](https://docs.lacunasoftware.com/articles/welcome/licensing#atualizar) vale para os dois casos.
## Como adquirir uma licença
| Produto | Como solicitar |
|---|---|
| PKI SDK | Licença de *trial* gratuita pelo site pki.lacunasoftware.com |
| Web PKI | Pelo [Fale conosco](https://www.lacunasoftware.com/pt/home/purchase) ou pelo e-mail [suporte@lacunasoftware.com](mailto:suporte@lacunasoftware.com), informando os *hostnames* ou URLs onde o componente será usado. As licenças do PKI SDK geralmente já vêm acompanhadas de uma licença do Web PKI |
| PKI Express | Licença para testes pelo [Fale conosco](https://www.lacunasoftware.com/pt/home/purchase) |
| Rest PKI *on-premises* | Licença de teste pelo [Fale conosco](https://www.lacunasoftware.com/pt/home/purchase) |
| Rest PKI Core e Signer *on-premises* | Usam as licenças do PKI SDK e do Web PKI (linhas acima) |
| Bulk Signer | A Lacuna fornece a licença (texto em Base64) junto com as credenciais de acesso ao produto |
| Rest PKI em nuvem | Crie uma conta em [pki.rest](https://pki.rest/) e gere um token de acesso no **Painel de Controle**. Veja os [preços](https://www.lacunasoftware.com/pt-br/pricing/#cloud-simulator) |
| Rest PKI Core em nuvem | Peça o endpoint e a chave de API ao [suporte ao desenvolvedor](mailto:suporte@lacunasoftware.com) |
Ao solicitar, recomendamos informar:
- o produto e o ambiente em que a licença será usada (produção, homologação ou testes);
- no caso do **Web PKI**, os *hostnames* ou URLs de produção e de homologação. As faixas de IP privado já vêm
liberadas para homologação (veja [Uso em IPs internos](https://docs.lacunasoftware.com/articles/web-pki/licensing#uso-em-ips-internos));
- se a licença é para **atualizar a versão** de um produto *on-premises*, a versão que você pretende instalar.
## Passo a passo para atualizar uma licença
1. **Identifique a licença e onde ela está configurada**, usando a tabela abaixo. Um mesmo produto pode usar mais de
uma licença: o Signer, por exemplo, usa a do PKI SDK e a do Web PKI.
2. **Se a troca acompanha uma atualização de versão**, consulte o histórico de versões (*changelog*) do produto entre
a versão atual e a de destino. Procure avisos como *"garanta que sua licença suporta versões do PKI SDK lançadas
até AAAA-MM-DD"*.
3. **Obtenha a nova licença** pelos canais de [Como adquirir uma licença](https://docs.lacunasoftware.com/articles/welcome/licensing#adquirir).
4. **Substitua o valor inteiro**, no mesmo lugar onde a licença antiga está configurada. Licenças em Base64 devem ser
coladas completas, em uma única linha. A licença JSON do Web PKI deve ser usada inteira.
5. **Reinicie a aplicação ou o serviço** nos produtos que leem a licença na inicialização (PKI SDK, Rest PKI Core,
Signer e Bulk Signer). No Rest PKI *on-premises*, a troca é feita pela tela de administração.
6. **Confirme que a nova licença foi carregada**, com a verificação indicada para o produto.
| Produto | Onde a licença fica | Como confirmar |
|---|---|---|
| [Web PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#web-pki-atualizar) | JavaScript da página: `new LacunaWebPKI(licença)` ou parâmetro `license` do `init()` | Nenhum erro de licença no componente. No formato JSON, confira `expiration` e os domínios |
| [Rest PKI *on-premises*](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-trocar) | **Administração** > **Status do sistema** > **SDK license loaded** | A mesma tela mostra o licenciado e a data de expiração |
| [Rest PKI Core *on-premises*](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-core-trocar) | Arquivo de configuração ou variáveis de ambiente (no Azure, `PkiSuite__SdkLicense` e `PkiSuite__WebLicense`) | O serviço volta a responder em `/api/system/info` (que mostra só a versão, não os dados da licença); teste uma assinatura |
| [PKI SDK](https://docs.lacunasoftware.com/articles/welcome/licensing#pki-sdk-atualizar) | `LacunaPKILicense.config`, `Web.config`/`App.config` ou `PkiConfig.LoadLicense` | `PkiInfo.License` (licenciado e expiração) |
| [PKI Express](https://docs.lacunasoftware.com/articles/welcome/licensing#pki-express-atualizar) | Ativação da máquina (`pkie activate` ou `pkiemgr.exe`) | Comandos executados sem os códigos de erro de ativação e licença (17 a 21) |
| [Signer *on-premises*](https://docs.lacunasoftware.com/articles/welcome/licensing#signer-trocar) | Seção `PkiSuite` (`SdkLicense` e `WebLicense`) | A partir da 2.2.0, licença do PKI SDK inválida impede a inicialização: confira `/api/system/info`. Para a do Web PKI, teste uma assinatura no navegador |
| [Bulk Signer](https://docs.lacunasoftware.com/articles/welcome/licensing#bulk-signer-trocar) | `Signing__PkiSdkLicense` (ou `Signing:PkiSdkLicense`) | Impressão digital da licença no *banner* de inicialização e no painel |
## Web PKI
O [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) é o componente que permite a um site usar os certificados digitais do computador do
usuário. A licença do Web PKI é **do site**: ela vale para os domínios da aplicação e é configurada pelo
desenvolvedor, no código da página.
### Quando a licença é necessária
A licença **não** é necessária:
- para testar em uma aplicação rodando no *localhost* (qualquer porta);
- quando o Web PKI é usado com o **Rest PKI em nuvem** (`https://pki.rest`), inclusive em produção.
Nos demais casos fora do *localhost*, a licença é necessária. Isso inclui:
- aplicações que usam o Web PKI com o [PKI SDK](https://docs.lacunasoftware.com/articles/welcome/licensing#pki-sdk), o [PKI Express](https://docs.lacunasoftware.com/articles/welcome/licensing#pki-express) ou qualquer outro
*backend* (as licenças do PKI SDK geralmente já vêm acompanhadas de uma licença do Web PKI);
- aplicações que usam uma [instância *on-premises* do Rest PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-on-premises), ou seja, um Rest PKI
diferente de `https://pki.rest`. Nesse caso, informe também a URL da instância em `restPkiUrl`;
- aplicações que usam o [Rest PKI Core](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-core), inclusive em nuvem (`core.pki.rest`): a dispensa vale
apenas para o Rest PKI em nuvem (`https://pki.rest`).
A licença do Web PKI também entra na configuração do servidor de produtos *on-premises*: no
[Signer](https://docs.lacunasoftware.com/articles/welcome/licensing#signer-licencas), ela consta como obrigatória nas instalações em Linux e Azure; no
[Rest PKI Core](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-core-licencas), ela é necessária quando os usuários assinam no navegador via Web PKI.
**Homologação:** as licenças são emitidas permitindo uso em caráter de homologação em qualquer IP privado
(`10.x.x.x`, `127.x.x.x`, `172.16.0.0` a `172.31.255.255` e `192.168.x.x`). Domínios de homologação, como
`staging.patorum.com`, ficam no campo `homologDomains` da licença.
### Formatos da licença
Você recebe a licença em dois formatos equivalentes:
```javascript
// Licença binária
var webPkiLicense = 'ASYAanNma...Q==';
// Licença JSON
var webPkiLicense = {
"format": 1,
"allowedDomains": [
"www.patorum.com"
],
"homologDomains": [
"staging.patorum.com",
"ip4:10.0.0.0/8",
"ip4:127.0.0.0/8",
"ip4:172.16.0.0/12",
"ip4:192.168.0.0/16"
],
"productLevel": "Standard",
"expiration": null,
"signature": "LzdT1cgp...w=="
};
```
- O formato **binário** esconde os detalhes da licença. Eles não ficam cifrados, apenas codificados em Base64.
- O formato **JSON** deixa os detalhes legíveis, o que facilita o diagnóstico de problemas como uma licença expirada.
- Nas configurações de servidor do Rest PKI Core e do Signer, use a licença **binária**.
> **Aviso**
>
> O campo `signature` da licença JSON não funciona sozinho como licença. Use a licença binária inteira ou a licença
> JSON inteira: qualquer tentativa de usar só uma parte da licença faz o componente rejeitá-la.
### Como configurar
Passe a licença no construtor da classe `LacunaWebPKI`:
```javascript
var pki = new LacunaWebPKI('ASYAanNma...Q=='); // ou o objeto JSON completo
pki.init({
ready: onWebPkiReady
});
```
A licença também pode ser informada no parâmetro `license` do método `init()`, caso não tenha sido passada no
construtor. Com um Rest PKI diferente de `https://pki.rest`, informe também a URL da instância:
```javascript
var pki = new LacunaWebPKI('ASYAanNma...Q==');
pki.init({
restPkiUrl: 'https://YourRestPKI.com/',
ready: onWebPkiReady
});
```
### Como atualizar a licença
1. Solicite a nova licença pelo e-mail [suporte@lacunasoftware.com](mailto:suporte@lacunasoftware.com) ou pelo
[Fale conosco](https://www.lacunasoftware.com/pt/home/purchase) (por vencimento ou para incluir um domínio, por
exemplo), informando os *hostnames* ou URLs de produção e de homologação.
2. Substitua a licença antiga pela nova **no mesmo lugar** em que ela é informada: no construtor
`new LacunaWebPKI(...)`, no parâmetro `license` do `init()` ou, na
[substituição do ICPBravoAccess](https://docs.lacunasoftware.com/articles/web-pki/icpbravo-replacement), em `window.lacunaWebPkiLicense`. Troque a
licença inteira.
3. Publique a página e teste nos domínios de homologação e de produção.
4. Se a licença do Web PKI também estiver configurada em um produto *on-premises*, troque-a lá:
[Rest PKI Core](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-core-trocar) e [Signer](https://docs.lacunasoftware.com/articles/welcome/licensing#signer-trocar).
> **Observação**
>
> Não confunda a licença com a versão do componente. O Web PKI é atualizado automaticamente nos computadores dos
> usuários, e a biblioteca JavaScript (`lacuna-web-pki-{version}.js`) deve ser atualizada pelo desenvolvedor. Veja
> [Atualizações do Web PKI](https://docs.lacunasoftware.com/articles/web-pki/update).
### Erros de licença
Quando há um problema com a licença, o componente retorna um destes códigos de erro
([`LacunaWebPKI.ErrorCodes`](https://docs.lacunasoftware.com/content/typedocs/web-pki/enums/_lacuna_web_pki_d_.lacunawebpki.errorcodes.html)):
| Código | Significado |
|---|---|
| `license_not_set` | A licença é necessária e não foi configurada |
| `license_invalid` | A licença informada não é válida |
| `license_restricted` | A licença não permite o comando solicitado |
| `license_expired` | A licença expirou |
| `license_domain_not_allowed` | A licença não permite o domínio em uso |
**Artigos de origem:** [Licenciamento do Web PKI](https://docs.lacunasoftware.com/articles/web-pki/licensing) ·
[Primeiros passos](https://docs.lacunasoftware.com/articles/web-pki/get-started) ·
[Especificando a URL do Rest PKI](https://docs.lacunasoftware.com/articles/web-pki/customizing-restpki-url) ·
[Atualizações](https://docs.lacunasoftware.com/articles/web-pki/update)
## Rest PKI
> **Observação: Rest PKI e Rest PKI Core são produtos diferentes**
>
> Cada um tem a sua documentação, a sua nuvem (`pki.rest` e `core.pki.rest`) e a sua forma de acesso. Esta seção trata
> do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki). O Rest PKI Core tem [seção própria](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-core).
### Rest PKI em nuvem
O Rest PKI em nuvem não usa arquivo de licença. O acesso é feito com um **token de acesso** da sua conta:
1. Autentique-se no site do Rest PKI ([pki.rest](https://pki.rest/)).
2. No **Painel de Controle**, clique em **Gerar novo token de acesso** e copie o valor gerado.
3. Configure o token na sua aplicação (nos projetos de exemplo, no arquivo de configuração, como o `web.config`).
Nas chamadas HTTP diretas, como as de carimbo de tempo, envie-o no header `Authorization: Bearer `.
A resposta **401 Unauthorized** indica que o token informado não é válido. Com o Rest PKI em nuvem, o Web PKI não
precisa de licença, inclusive em produção.
### Rest PKI *on-premises*
Uma instância *on-premises* do Rest PKI, em Windows Server ou Azure App Service, exige uma **licença do Lacuna PKI
SDK**, solicitada durante a instalação. Desde a versão 1.5.0, o Rest PKI também aceita licenças do PKI SDK de uso
restrito ao Rest PKI. Para instalar, você precisa de:
- o pacote de binários do Rest PKI;
- a licença binária do PKI SDK (arquivo `LacunaPkiLicense.txt`). Se ainda não tiver uma,
[solicite uma licença de teste](https://www.lacunasoftware.com/pt/home/purchase).
No Windows Server, a instalação segue o capítulo 2 do
[Manual de instalação](https://cdn.lacunasoftware.com/restpki/restpki-admin-guide-pt.pdf). No Azure App Service, com o
pacote e a licença em mãos, [entre em contato](https://www.lacunasoftware.com/pt/home/purchase) para obter as
instruções de instalação.
Se a sua aplicação usa o Web PKI com a instância *on-premises*, configure também a **licença do Web PKI** e a URL da
instância (veja [Como configurar o Web PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#web-pki-configurar)).
A partir da versão 1.14.0, o painel do Rest PKI exibe um aviso quando a licença do PKI SDK está próxima do vencimento.
A versão 1.12.0 corrigiu um erro que ocorria ao alterar a licença do PKI SDK; em instâncias mais antigas, considere
atualizar antes de trocar a licença.
### Como trocar a licença
1. Autentique-se como administrador na sua instância do Rest PKI. Se não souber a senha, veja
[Recuperação de senha de admin](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/admin-recover).
2. No menu superior, clique em **Administração** > **Status do sistema**.
3. No item **SDK license loaded**, clique no botão **Alterar**.
4. No arquivo `LacunaPkiLicense.txt`, localize a licença binária do PKI SDK, na seção
*Binary license content (Base64-encoded)*:
```text
Binary license content (Base64-encoded)
---------------------------------------
AxAA........................................iw==
```
5. Copie e cole **a linha inteira** e clique no botão **Configurar licença**.

A mesma tela mostra o licenciado e a data de expiração da licença carregada.
**Artigos de origem:** [Alterando a licença de uso](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/change-license) ·
[Rest PKI *on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) ·
[Instalação em Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup) ·
[Instalação em Azure App Service](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/azure-setup) ·
[Solicitando carimbos de tempo](https://docs.lacunasoftware.com/articles/rest-pki/requesting-timestamps)
## Rest PKI Core
O [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) pode ser usado em nuvem (SaaS), no endereço
[core.pki.rest](https://core.pki.rest/), ou em uma instância própria (*on-premises*).
### Rest PKI Core em nuvem
No SaaS, a aplicação não usa arquivo de licença. São necessários dois parâmetros, que devem ser solicitados ao
[suporte ao desenvolvedor](mailto:suporte@lacunasoftware.com):
- o **endpoint** da API;
- a **chave de API**.
Em ASP.NET Core, os dois ficam na seção `RestPki` do `appsettings.json` (chaves `Endpoint` e `ApiKey`). Chamando a
API diretamente, a chave de API vai no header `X-Api-Key` (nos endpoints de carimbo de tempo, use
`Authorization: ApiKey SUA_CHAVE_DE_API`; veja [Solicitando carimbos de tempo](https://docs.lacunasoftware.com/articles/rest-pki/core/requesting-timestamps)). A resposta **401** indica chave ausente ou inválida, e a
**403**, que a aplicação não tem permissão para a operação.
Se a sua aplicação usa o Web PKI com o Rest PKI Core, a licença do Web PKI é necessária: a dispensa vale apenas para o
Rest PKI em nuvem (`https://pki.rest`).
### Licenças da instância *on-premises*
Uma instância *on-premises* precisa de duas licenças:
- **licença do PKI SDK**, em Base64;
- **licença do Web PKI**, em formato binário (Base64).
| Plataforma | Onde as licenças são configuradas |
|---|---|
| [Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/windows-server) | Arquivo `appsettings.ini` na pasta `C:\ProgramData\Lacuna Software\Rest PKI Core` (não a cópia em `config-templates`) |
| [Linux (Ubuntu)](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/install-ubuntu) | Arquivo `/etc/restpkicore/appsettings.conf` |
| [Docker](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/docker) | Variáveis de ambiente (arquivo de exemplo `restpkicore.env`) |
| [Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure/install) | *Configuration* do App Service: `PkiSuite__SdkLicense` e `PkiSuite__WebLicense` |
No Azure, a `PkiSuite__SdkLicense` é obrigatória, e a `PkiSuite__WebLicense` só é obrigatória se os usuários forem
assinar documentos com certificados em seus computadores (assinatura no navegador via Web PKI). Nas demais
plataformas, as duas licenças constam como pré-requisito da instalação. Para Rocky Linux, veja a
[instalação no Rocky Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/install-rocky).
Nos arquivos de configuração (Windows, Linux e Docker), preencha as licenças seguindo as instruções do próprio arquivo
de modelo. Nas variáveis de ambiente, o nome é formado pela seção e pela configuração separadas por `__` (dois
*underscores*): a configuração `SdkLicense` da seção `PkiSuite`, por exemplo, vira `PkiSuite__SdkLicense`.
### Como trocar a licença
> **Observação**
>
> A documentação do Rest PKI Core não tem um artigo específico sobre troca de licença. Os passos abaixo seguem os
> procedimentos de configuração e de reinício de cada plataforma.
- **Linux**: edite `/etc/restpkicore/appsettings.conf`, substitua a licença e reinicie o serviço com
`systemctl restart restpkicore`. Acompanhe os logs com `journalctl -u restpkicore -f`.
- **Windows Server**: edite o `appsettings.ini`, substitua a licença e reinicie o site no IIS (*Stop* e *Start*).
- **Docker**: altere a licença no arquivo de variáveis de ambiente (por exemplo, `restpkicore.env`). Como as
variáveis são lidas na criação do contêiner, remova o contêiner atual (`docker rm -f restpkicore`) e crie-o
novamente com o mesmo comando `docker run --name restpkicore --env-file restpkicore.env ...` da instalação.
Confira os logs com `docker logs -f restpkicore`.
- **Azure App Services**: em *Configuration*, altere `PkiSuite__SdkLicense` e/ou `PkiSuite__WebLicense` e clique em
**Save**.
Depois da troca, confira se a instância responde em `https://endereco-do-seu-restpki-core/api/system/info`.
### Antes de atualizar a versão
Algumas versões exigem que a licença do PKI SDK suporte versões lançadas até uma certa data:
| Versão do Rest PKI Core | A licença do PKI SDK deve suportar versões lançadas até |
|---|---|
| [2.0.0 RC 8](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog#v2.0.0-rc08) | 2023-06-27 |
| [2.0.0 RC 7](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog#v2.0.0-rc07) | 2022-10-19 |
Antes de atualizar, consulte o [histórico de versões](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog) entre a sua versão e a de
destino. A versão instalada aparece em `/api/system/info` (veja
[Verificando a versão instalada](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/check-version)).
**Artigos de origem:** [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) ·
[Primeiros passos para integração](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started) ·
[Ambientes *on premises*](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises#platforms) ·
[Setup em Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure/install) ·
[Histórico de versões](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog)
## PKI SDK
O [Lacuna PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk) é uma biblioteca .NET, distribuída pelo NuGet (pacote `Lacuna.Pki`). Para
usá-lo, é necessário carregar a licença na inicialização da aplicação.
### Como obter a licença
Se você ainda não tem uma licença, entre em contato pelo site pki.lacunasoftware.com para obter uma licença de *trial*
gratuita. A licença pode ser usada em dois formatos:
- arquivo `LacunaPKILicense.config`;
- texto em Base64.
Se a sua aplicação faz assinatura no navegador, ela também usa o [Web PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#web-pki), que precisa de licença própria
fora do *localhost*. As licenças do PKI SDK geralmente já vêm acompanhadas de uma licença do Web PKI.
### Como carregar a licença
Existem três formas. A recomendada depende do tipo de aplicação:
| Forma | Recomendada para |
|---|---|
| Arquivo `LacunaPKILicense.config` no diretório da DLL | Aplicações web |
| Chave no `Web.config` ou no `App.config` | — |
| Programaticamente, com `PkiConfig.LoadLicense` | Aplicações desktop |
**1. Arquivo no diretório da DLL.** O SDK procura automaticamente o arquivo `LacunaPKILicense.config` junto à
`Lacuna.Pki.dll`. Uma forma prática é incluir o arquivo no projeto com **Build Action** = **Content** e
**Copy to Output Directory** = **Copy Always**.
**2. `Web.config` ou `App.config`.** Na seção `appSettings`, informe o caminho do arquivo ou a licença em Base64:
```xml
```
**3. Programaticamente.** No código de inicialização do site ou da aplicação, carregue a licença decodificada:
```csharp
byte[] binLicense = Convert.FromBase64String("[licença em Base64]");
PkiConfig.LoadLicense(binLicense);
```
Essa forma deixa a licença embutida no código-fonte, o que dificulta o vazamento para terceiros. Também existe
`PkiConfig.LoadLicense(string)`, que recebe o caminho do arquivo `.config`, mas ela não é recomendada para aplicações
desktop.
### Como atualizar a licença
> **Observação**
>
> A página de licenciamento do PKI SDK não descreve a troca de licença. Como a licença é carregada na inicialização, o
> procedimento é substituí-la onde ela foi configurada e reiniciar a aplicação.
1. Substitua a licença no mesmo lugar em que ela foi configurada: o arquivo `LacunaPKILicense.config`, a chave
`LacunaPKI.LicensePath` ou `LacunaPKI.BinaryLicense`, ou o valor passado a `PkiConfig.LoadLicense`.
2. Se a licença está incluída no projeto ou embutida no código, recompile e publique a aplicação.
3. Reinicie a aplicação ou o site.
4. Para conferir a licença carregada, use a propriedade `PkiInfo.License` (classe
[`PkiInfo`](https://docs.lacunasoftware.com/api/Lacuna.Pki.Util/PkiInfo)), que informa o licenciado (`Licensee`) e a data de expiração
(`Expiration`).
Nos produtos *on-premises* que usam a licença do PKI SDK, a troca é feita na configuração de cada produto: veja
[Rest PKI](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-trocar), [Rest PKI Core](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-core-trocar), [Signer](https://docs.lacunasoftware.com/articles/welcome/licensing#signer-trocar) e
[Bulk Signer](https://docs.lacunasoftware.com/articles/welcome/licensing#bulk-signer-trocar).
### Licença e versões do SDK
A licença do PKI SDK pode ter data de expiração (confira em `PkiInfo.License.Expiration`) e aceita apenas versões do
SDK lançadas até uma certa data. Por isso, os
produtos que usam o PKI SDK avisam, no histórico de versões, qual cobertura a licença precisa ter antes de uma
atualização (veja [Signer](https://docs.lacunasoftware.com/articles/welcome/licensing#signer-versao) e [Rest PKI Core](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki-core-versao)).
**Artigos de origem:** [Licenciamento do PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/licensing) ·
[Acessando pacotes NuGet](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/nuget) ·
[Assinatura no browser](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/web-remote)
## PKI Express
O [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) é instalado em Windows ou Linux e **precisa de uma licença**: o arquivo
`LacunaPkiLicense.config`, usado para ativar o PKI Express na máquina.
### Como obter a licença
[Fale conosco](https://www.lacunasoftware.com/pt/home/purchase) para obter uma licença para testes.
### Ativação no Windows
1. Instale o PKI Express com o instalador ou, sem permissões de administrador, com um dos pacotes zip (veja
[Instalação em Windows](https://docs.lacunasoftware.com/articles/pki-express/setup/windows)).
2. Abra a pasta de instalação e execute o **PKI Express Configuration Manager** (arquivo `pkiemgr.exe`). Não há
atalho no menu Iniciar: é preciso navegar até a pasta.
3. Siga as instruções do aplicativo para ativar o PKI Express. No Windows, tanto a ativação pela rede quanto a manual
são feitas por esse aplicativo.
### Ativação no Linux
Depois de instalar o pacote e configurar a pasta de logs (veja [Ubuntu ou Mint](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-ubuntu#install),
[Red Hat Enterprise Linux](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-rhel#install) ou
[CentOS, Oracle Linux ou Fedora](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-centos#install)), ative o PKI Express com o arquivo de
licença:
```sh
sudo pkie activate LacunaPkiLicense.config
```
Se a ativação automática falhar, faça a [ativação manual](https://docs.lacunasoftware.com/articles/welcome/licensing#pki-express-manual).
### Ativação manual (Linux)
1. Gere o pedido de ativação:
```sh
pkie activate LacunaPkiLicense.config --request
```
Um arquivo chamado `pkie-activation-request.pem` é salvo na pasta atual.
2. Envie esse arquivo pela [Central de Suporte](http://lacuna.help).
3. Você receberá de volta o arquivo de ativação `pkie-activation.pem`. Copie-o para a pasta atual e execute:
```sh
sudo pkie activate
```
Ou, se preferir, informe o caminho do arquivo:
```sh
sudo pkie activate --file /path/to/pkie-activation.pem
```
### Trocar a licença e atualizar a versão
> **Observação**
>
> A documentação do PKI Express não tem um artigo específico sobre troca de licença. Para aplicar uma nova licença,
> refaça a ativação com o novo arquivo `LacunaPkiLicense.config`: no Linux, com
> `sudo pkie activate LacunaPkiLicense.config` (ou pela [ativação manual](https://docs.lacunasoftware.com/articles/welcome/licensing#pki-express-manual)); no Windows, pelo
> `pkiemgr.exe`. A ativação manual com uma nova licença foi corrigida na versão
> [1.6.2](https://docs.lacunasoftware.com/articles/pki-express/changelog#v1-6-2); use essa versão ou superior.
Atualizar a **versão** do PKI Express é outro procedimento:
- **Windows**: baixe a nova versão e execute o instalador (veja [Atualização](https://docs.lacunasoftware.com/articles/pki-express/setup/windows#update)).
- **Linux**: baixe o novo pacote, apague o conteúdo da pasta de destino e extraia o novo pacote nela (veja
Atualização em [Ubuntu ou Mint](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-ubuntu#update),
[RHEL](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-rhel#update) ou
[CentOS, Oracle Linux ou Fedora](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-centos#update)). Se estiver atualizando a partir de uma versão 1.0.x,
refaça também a configuração da pasta de logs e a ativação.
### Códigos de retorno
| Código | Nome | Significado |
|---|---|---|
| 17 | `NotActivated` | PKI Express não ativado |
| 18 | `ActivationError` | Falha na ativação do PKI Express |
| 19 | `BadLicense` | Base64 inválida (da licença de ativação) |
| 20 | `LicenseNeeded` | Nenhum arquivo de licença fornecido |
| 21 | `InvalidLicense` | Licença inválida |
**Artigos de origem:** [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) ·
[Instalação](https://docs.lacunasoftware.com/articles/pki-express/setup) ·
[Ativação manual](https://docs.lacunasoftware.com/articles/pki-express/setup/manual-activation) ·
[Códigos de retorno](https://docs.lacunasoftware.com/articles/pki-express/return-codes)
## Signer
Uma instância *on-premises* do [Signer](https://docs.lacunasoftware.com/articles/signer) precisa de duas licenças:
- **licença do PKI SDK**, em Base64;
- **licença do Web PKI**, em formato binário (Base64).
### Onde as licenças são configuradas
| Plataforma | Onde configurar |
|---|---|
| [Linux (Ubuntu)](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/install-ubuntu) | Arquivo `/etc/lacuna-signer/appsettings.linux.json`, seção `PkiSuite`: `SdkLicense` e `WebLicense` |
| [Azure App Services](https://docs.lacunasoftware.com/articles/signer/on-premises/azure) | *Configuration* do App Service: `PKiSuite__SdkLicense` e `PKiSuite__WebLicense` |
| [Windows Server](https://docs.lacunasoftware.com/articles/signer/on-premises/windows) | Arquivo `appsettings.iis.json`, seção `PkiSuite`: `SdkLicense` e `WebLicense` |
| [Docker](https://docs.lacunasoftware.com/articles/signer/on-premises/docker) | Variáveis de ambiente `PkiSuite__SdkLicense` e `PkiSuite__WebLicense` |
Nas páginas de instalação em Linux e em Azure, as duas licenças são obrigatórias. Nos artigos de Windows Server e
Docker, apenas a `SdkLicense` consta como obrigatória, e a `WebLicense` aparece como condicional. De
qualquer forma, a licença do Web PKI é necessária para a [assinatura embutida](https://docs.lacunasoftware.com/articles/welcome/licensing#signer-embutida). A partir da versão 2.2.0, a
licença do PKI SDK é validada na inicialização: se ela não for válida, a inicialização é interrompida.
### Como trocar a licença
> **Observação**
>
> A documentação do Signer não tem um artigo específico sobre troca de licença. Os passos abaixo seguem os
> procedimentos de instalação e de atualização de cada plataforma.
- **Linux**: edite `/etc/lacuna-signer/appsettings.linux.json`, substitua `SdkLicense` e/ou `WebLicense` na seção
`PkiSuite` e reinicie o serviço com `systemctl restart lacuna-signer`. Confira com `systemctl status lacuna-signer`
e acompanhe os logs com `journalctl -u lacuna-signer -f`.
- **Azure App Services**: em *Configuration*, altere `PKiSuite__SdkLicense` e/ou `PKiSuite__WebLicense` e clique em
**Save**.
- **Windows Server**: pare o site no IIS, altere a seção `PkiSuite` do `appsettings.iis.json` e inicie o site
novamente.
- **Docker**: altere as variáveis `PkiSuite__SdkLicense` e/ou `PkiSuite__WebLicense` e recrie o contêiner.
Depois da troca, confira se a instância responde em `/api/system/info` (no Linux, por exemplo,
`curl http://localhost:5001/api/system/info`).
### Antes de atualizar a versão do Signer
Algumas versões exigem que a licença do PKI SDK suporte versões do SDK lançadas até uma certa data. Confira o
[histórico de versões](https://docs.lacunasoftware.com/articles/signer/changelog) entre a versão atual e a de destino:
| Versão do Signer | A licença deve suportar versões do PKI SDK lançadas |
|---|---|
| [2.2.0](https://docs.lacunasoftware.com/articles/signer/changelog#v2-2-0) | até 2025-08-01 |
| [2.0.0](https://docs.lacunasoftware.com/articles/signer/changelog#v2-0-0) | até 2025-05-20 |
| [1.72.0](https://docs.lacunasoftware.com/articles/signer/changelog#v1-72-0) | até 2024-07-03 |
| [1.64.0](https://docs.lacunasoftware.com/articles/signer/changelog#v1-64-0) | até 2023-09-27 |
| [1.61.0](https://docs.lacunasoftware.com/articles/signer/changelog#v1-61-0) | até 2023-08-01 |
| [1.59.0](https://docs.lacunasoftware.com/articles/signer/changelog#v1-59-0) | até 2023-06-28 |
| [1.58.0](https://docs.lacunasoftware.com/articles/signer/changelog#v1-58-0) | até 2023-05-25 |
| [1.43.0](https://docs.lacunasoftware.com/articles/signer/changelog#v1-43-0) | após 2022-04-29 |
Se a sua licença não cobre a data exigida, obtenha uma licença atualizada do PKI SDK antes de atualizar.
### Assinatura embutida
Para usar a [assinatura embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature) (componente `LacunaSignerWidget`) em outra
aplicação, o Signer deve estar configurado com uma licença do Web PKI que tenha o domínio no qual o componente será
carregado. Para incluir um novo domínio, obtenha uma nova licença do Web PKI e troque-a conforme
[Como trocar a licença](https://docs.lacunasoftware.com/articles/welcome/licensing#signer-trocar).
**Artigos de origem:** [Instalação em Ubuntu Server](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/install-ubuntu) ·
[Setup em Azure App Services](https://docs.lacunasoftware.com/articles/signer/on-premises/azure) ·
[Atualização em Linux](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/update) ·
[Histórico de versões](https://docs.lacunasoftware.com/articles/signer/changelog)
## Bulk Signer
O [Bulk Signer](https://docs.lacunasoftware.com/articles/bulk-signer) usa uma única licença: a **licença do Lacuna PKI SDK**, um texto em Base64
fornecido pela Lacuna Software. Ela é obrigatória: sem ela, o serviço não inicia.
### Como obter
A Lacuna entrega três itens, que não são intercambiáveis:
- as credenciais do *registry* (usuário e token de acesso), para baixar a imagem de contêiner;
- um identificador único, usado nas URLs de download dos binários;
- a licença do PKI SDK, que libera o serviço em execução.
Como a licença é apenas um texto, e não um download, ela também funciona em servidores isolados da rede.
### Onde configurar
A forma preferida é a variável de ambiente `Signing__PkiSdkLicense`. Também é possível usar a chave
`Signing:PkiSdkLicense` no `appsettings.Production.json`, desde que o arquivo esteja fora do controle de versão e
com acesso restrito à conta de serviço. A variável é preferida porque mantém a licença fora da árvore de arquivos, e
ela tem precedência na inicialização.
| Alvo | Onde definir `Signing__PkiSdkLicense` | Como aplicar |
|---|---|---|
| Linux (systemd) | Arquivo `/etc/bulksigner/bulksigner.env` (modo `0640`, dono `bulksigner`) | `sudo systemctl restart bulksigner` |
| Windows (serviço) | Variável de ambiente de escopo de máquina | `Restart-Service LacunaBulkSigner` |
| Docker / Compose | Arquivo `deploy/docker/.env` | `docker compose up -d` |
| Azure App Service | *App settings*, como referência ao Key Vault | Veja [Azure App Service](https://docs.lacunasoftware.com/articles/bulk-signer/azure) |
No Windows, defina a variável em um PowerShell com privilégios elevados:
```powershell
[Environment]::SetEnvironmentVariable("Signing__PkiSdkLicense", "", "Machine")
Restart-Service LacunaBulkSigner
```
> **Aviso: Atualizando a partir da 1.0.x**
>
> Na versão 1.0.x, a chave se chamava `Signing:License` (`Signing__License`). Ela foi renomeada na 1.1.0 e o nome
> antigo não é mais lido: uma instalação atualizada que ainda usa o nome antigo falha na inicialização com
> `Signing:PkiSdkLicense is required`. Renomeie a chave durante a atualização.
### Como trocar a licença
1. Anote a impressão digital da licença atual: linha `pki license` do *banner* de inicialização, ou página
**Sistema** (`/system`) do painel, campo **Impressão digital da licença**.
2. Substitua o valor de `Signing__PkiSdkLicense` no local indicado para o seu alvo (tabela acima). Se você usa
`Signing:PkiSdkLicense` no `appsettings.Production.json`, verifique se não há uma variável de ambiente antiga
definida, porque ela tem precedência.
3. Reinicie o serviço.
4. Confira se a impressão digital da licença mudou e se o endpoint `/api/ready` não aponta falha na verificação
`license`.
### Diagnóstico
- **`Signing:PkiSdkLicense is required`**: nem a variável de ambiente nem a configuração têm valor. Defina a variável
no alvo e reinicie o serviço.
- **`/api/ready` retorna 503 com falha na verificação `license`**: a licença não foi carregada. Se a impressão
digital não aparece no *banner*, a licença foi rejeitada na inicialização.
**Artigos de origem:** [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation) ·
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration) ·
[Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) ·
[Resolução de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting)
## Para usuários finais
Esta seção é para quem usa um site ou sistema que pede o **Web PKI**, a extensão de navegador da Lacuna, para assinar
documentos ou fazer login com certificado digital.
### Preciso comprar ou configurar uma licença?
Não. Quando o Web PKI exige licença, ela é do site: vale para os domínios do site e é configurada por quem o
desenvolve, no código da página (sites que usam o Rest PKI em nuvem nem precisam dela). Quem apenas instala a extensão para usar o site não compra nem configura licença.
Se o site exibir um erro de licença do Web PKI (por exemplo, licença expirada ou domínio não permitido), o problema
está na configuração do site. Informe a empresa responsável por ele.
### Certificado digital: quem emite?
O Web PKI não emite certificados digitais. Ele permite que o site use um certificado que você já tem, seja um
certificado A1, instalado no computador, seja um A3, em um dispositivo criptográfico como token ou cartão.
Certificados ICP-Brasil são adquiridos de uma Autoridade Certificadora (AC) credenciada, que consta na
[lista oficial do ITI](https://www.gov.br/iti/pt-br/assuntos/icp-brasil/entidades-icp-brasil). Dúvidas sobre compra,
validade ou renovação do certificado devem ser tratadas com a AC que o emitiu.
Para conferir, no Windows, se há um certificado com chave privada no computador:
1. **Iniciar** > **Executar...** > **certmgr.msc**
2. Na pasta **Pessoal**, dê um duplo clique no certificado.
3. Deve aparecer a mensagem "Tem uma chave particular correspondente a este certificado."
### Instalação do Web PKI
- Se o componente não estiver instalado, estiver desatualizado ou o navegador não for suportado, normalmente o site
leva você à página de instalação e, ao final, você volta automaticamente ao site (o site pode exibir antes uma
mensagem própria).
- A instalação do Web PKI é feita apenas a partir de [get.webpkiplugin.com](https://get.webpkiplugin.com): a extensão
vem da loja oficial do navegador, e o aplicativo nativo é baixado desse mesmo domínio.
- Depois de instalado, o Web PKI é atualizado automaticamente.
- Veja os [navegadores suportados](https://docs.lacunasoftware.com/articles/web-pki/browser-support).
### Onde pedir ajuda
- **Erros de licença ou dúvidas sobre o serviço do site**: fale com a empresa responsável pelo site. Pedidos sobre os
seus dados pessoais também devem ser feitos a ela, que é a controladora dos dados.
- **Problemas para instalar ou usar o Web PKI**: visite a [Central de Suporte](http://lacuna.help/) da Lacuna.
- Ao pedir suporte, ajuda ativar os logs detalhados: clique no ícone da extensão e vá em
**Configurações** > **Avançadas** > **Logs detalhados** (veja [Logs](https://docs.lacunasoftware.com/articles/web-pki/logs)).
## Contatos
| Canal | Para quê |
|---|---|
| [Fale conosco](https://www.lacunasoftware.com/pt/home/purchase) | Solicitar licenças, licenças de teste e informações comerciais |
| [suporte@lacunasoftware.com](mailto:suporte@lacunasoftware.com) | Suporte ao desenvolvedor, pedido de licença do Web PKI e parâmetros do Rest PKI Core em nuvem |
| [Central de Suporte](http://lacuna.help/) | Usuários finais e ativação manual do PKI Express |
| pki.lacunasoftware.com | Licença de *trial* gratuita do PKI SDK |
| +55 61 3030-5700 | Telefone. Endereço: EQN 102/103, Ed. Avenida 102, 2º andar, Asa Norte, Brasília-DF |
# [Contribuindo](https://docs.lacunasoftware.com/articles/welcome/contributing)
No momento, não estamos aceitando contribuições públicas, mas em breve passaremos a aceitar pull requests. Por enquanto,
se você encontrar um erro ou notar que falta algo neste portal de documentação,
[abra uma issue](https://github.com/LacunaSoftware/docs/issues)
## Criando um novo artigo
Siga estas orientações para criar um novo artigo:
1. Crie o artigo **nos dois projetos, en-us e pt-br**, com codificação UTF-8 com BOM
1. Altere o `toc.md` **dos dois projetos**, incluindo o artigo
> **Dica**
>
> Uma forma fácil de garantir que o novo artigo tenha codificação UTF-8 com BOM é copiar um artigo existente em vez de criar um arquivo novo.
Você não precisa escrever as duas versões do artigo de imediato: pode escrever uma das versões e colocar isto na outra:
```md
# Article title
{/* link to version in Portuguese */}
```
ou
```md
# Título do Artigo
{/* link to version in English */}
```
## Escrevendo documentação
Estes são os links mais importantes sobre como escrever documentação:
* [Mastering Markdown - GitHub Guides](https://guides.github.com/features/mastering-markdown/)
* [DocFX Flavored Markdown - site do DocFX](https://dotnet.github.io/docfx/spec/docfx_flavored_markdown.html)
* [Links and Cross References - site do DocFX](https://dotnet.github.io/docfx/tutorial/links_and_cross_references.html)
* [Overwrite Files - site do DocFX](https://dotnet.github.io/docfx/tutorial/intro_overwrite_files.html)
## Guia rápido de Markdown
Esta é a sintaxe dos elementos mais comuns:
* [Títulos](https://docs.lacunasoftware.com/articles/welcome/contributing#títulos)
* [Negrito](https://docs.lacunasoftware.com/articles/welcome/contributing#negrito)
* [Itálico](https://docs.lacunasoftware.com/articles/welcome/contributing#itálico)
* [Código inline](https://docs.lacunasoftware.com/articles/welcome/contributing#código-inline)
* [Listas](https://docs.lacunasoftware.com/articles/welcome/contributing#listas)
* [Linhas horizontais](https://docs.lacunasoftware.com/articles/welcome/contributing#linhas-horizontais)
* [Links](https://docs.lacunasoftware.com/articles/welcome/contributing#links)
* [Trechos de código](https://docs.lacunasoftware.com/articles/welcome/contributing#trechos-de-código)
* [Includes](https://docs.lacunasoftware.com/articles/welcome/contributing#includes)
* [Inclusão de trechos de código](https://docs.lacunasoftware.com/articles/welcome/contributing#inclusão-de-trechos-de-código)
* [Imagens](https://docs.lacunasoftware.com/articles/welcome/contributing#imagens)
* [Citações, notas e avisos](https://docs.lacunasoftware.com/articles/welcome/contributing#citações-notas-e-avisos)
* [Tabelas](https://docs.lacunasoftware.com/articles/welcome/contributing#tabelas)
### Títulos
Título de nível superior (H1):
```md
# Article title
```
Título de nível 2 (H2):
```md
## Section title
```
Título de nível 3 (H3):
```md
### Subsection title
```
### Negrito
```md
This is a **bold** statement
```
This is a **bold** statement
### Itálico
```md
This is a *foreign word*.
```
This is a *foreign word*.
### Código inline
```md
Pass the `callback` parameter
```
Pass the `callback` parameter
### Listas
Não ordenada:
```md
* Item A
* Item B
* Subitem (prefix spaces to align with parent)
* And so on
* Item C
```
* Item A
* Item B
* Subitem (prefix spaces to align with parent)
* And so on
* Item C
Ordenada:
```md
1. Item 1
1. Item 2
1. Subitem (prefix spaces to align with parent)
1. And so on
1. Item 3
```
1. Item 1
1. Item 2
1. Subitem (prefix spaces to align with parent)
1. And so on
1. Item 3
> A numeração em si não importa: o DocFX ordena a lista ao renderizar o arquivo. Prefira repetir `1.` em todos os itens em vez de escrever `1.`, `2.`, `3.` etc., porque isso facilita reordenar os itens.
### Linhas horizontais
Use `***` para inserir uma linha horizontal:
```md
Something
***
Some other thing
```
> **Observação**
>
> **Não** use a sintaxe `---`, pois ela tem um significado especial no DocFX
### Links
```md
[text](url)
```
Para artigos na mesma pasta:
```md
See the article [Welcome](index.md)
```
See the article [Welcome](https://docs.lacunasoftware.com/articles/welcome)
Para artigos em outras pastas:
```md
You can use the [Lacuna PKI SDK](../pki-sdk/index.md) for this purpose
```
You can use the [Lacuna PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk) for this purpose
Para classes:
```md
See class @Lacuna.RestPki.Client.RestPkiClient
```
See class @Lacuna.RestPki.Client.RestPkiClient
Para métodos:
```md
See method @Lacuna.RestPki.Client.RestPkiClient.GetPkiBrazilTimestamper
```
See method @Lacuna.RestPki.Client.RestPkiClient.GetPkiBrazilTimestamper
> Para descobrir os UIDs exatos de classes e métodos, veja os arquivos .yml da pasta `api` depois de compilar o projeto docfx.
Em alguns casos, a sintaxe `@uid` não funciona para referências cruzadas. Nesses casos, use:
```md
```
Se o uid contiver um `#`, codifique-o como `%23`:
```md
```
Links para seções de artigos:
Primeiro, você precisa declarar o nome da âncora logo antes do título da seção:
```md
## Section title \{#anchor-name}
```
> **Observação**
>
> Se o artigo estiver em inglês e o título da seção corresponder ao nome da âncora (convertido para minúsculas e com os espaços substituídos por hifens), você
> não precisa declarar a âncora
Depois, para criar o link para a seção:
```md
See [Default error callback](../web-pki/advanced.md#default-error-callback)
```
See [Default error callback](https://docs.lacunasoftware.com/articles/web-pki/advanced#default-error-callback)
### Trechos de código
```languageid
code
```
IDs de linguagem comuns:
* csharp ou cs
* javascript ou js
* html
Trechos de código podem ser indentados dentro de listas:
1. Instalação
1. Prepare o ambiente
1. Execute isto:
```bash
sudo apt install something
```
1. Finalize
1. Pronto
Veja todos os [nomes e aliases de linguagem](https://github.com/highlightjs/highlight.js#supported-languages) suportados.
### Includes
```md
{/* include: url */}
```
Blocos de include comuns aos dois idiomas ficam na pasta [includes/](https://github.com/LacunaSoftware/docs/tree/master/includes). Por exemplo, para
incluir o bloco [rest-pki-samples-matrix.md](https://github.com/LacunaSoftware/docs/blob/master/includes/rest-pki-samples-matrix.md) a partir de um local
como `docs.en-us/articles/project-name/article.md`:
```md
{/* include: ../../../includes/rest-pki-samples-matrix.md */}
```
Exemplo: [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki)
### Inclusão de trechos de código
* [Sintaxe](https://dotnet.github.io/docfx/spec/docfx_flavored_markdown.html#code-snippet)
* Exemplo: @Lacuna.Pki.Cades.CadesSignatureCompression \[[código-fonte](https://github.com/LacunaSoftware/docs/blob/master/docs.en-us/apidoc/Lacuna.Pki.Cades.CadesSignatureCompression.md)\]
### Imagens
```md

```
`url` pode ser uma URL remota, mas normalmente é uma referência relativa a uma imagem da pasta [images/](https://github.com/LacunaSoftware/docs/tree/master/images).
Por exemplo, para referenciar a imagem [images/pki-sdk/pades-rectangle.png](https://github.com/LacunaSoftware/docs/blob/master/images/pki-sdk/pades-rectangle.png) a partir dos seguintes locais:
A partir de `docs.en-us/articles/project-name/article.md`:
```md

```
A partir de `docs.en-us/articles/project-name/subfolder/article.md`:
```md

```
Exemplos:
* [PKI SDK / Primeiros passos / Adicionando o pacote nuget](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/nuget)
* [Web PKI / Browsers suportados](https://docs.lacunasoftware.com/articles/web-pki/browser-support)
### Citações, notas e avisos
Citações:
```md
As Jeffrey Lebowski once said:
> Far out.
```
As Jeffrey Lebowski once said:
> Far out.
Notas:
```md
:::note
This is an important notice
:::
```
> **Observação**
>
> This is an important notice
Avisos:
```md
:::warning
Watch out!
:::
```
> **Aviso**
>
> Watch out!
Também são suportados:
> **Dica**
>
> Isto é uma dica
> **Info**
>
> Isto é importante
> **Cuidado**
>
> Prossiga com cautela
### Tabelas
```md
First Header | Second Header
--------------------------- | -------------
Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column
```
First Header | Second Header
--------------------------- | -------------
Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column
Não é realmente necessário alinhar as colunas; isto gera exatamente o mesmo resultado:
```md
First Header | Second Header
------------ | -------------
Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column
```
First Header | Second Header
------------ | -------------
Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column
Para casos mais complexos, por exemplo com a célula superior esquerda em branco, use esta sintaxe um pouco mais complexa:
```md
| | Second Header |
| --- | ------------- |
| Content from cell 1 | Content from cell 2 |
| Content in the first column | Content in the second column |
```
| | Second Header |
| --- | ------------- |
| Content from cell 1 | Content from cell 2 |
| Content in the first column | Content in the second column |
> A especificação do Markdown do GitHub (que o DocFX estende) exige pelo menos três hifens (`---`) em cada coluna da linha de cabeçalho (como mostrado acima, na primeira coluna).
A linha de cabeçalho é sempre renderizada em negrito. Se você também precisar da primeira coluna em negrito, faça assim:
```md
| | Ease of use | Awesomeness |
| ------------ | ------------- | ------------ |
| **Web PKI** | Very easy | So good |
| **Rest PKI** | Super easy | Much awesome |
| **PKI SDK** | Easy peasy | Wow |
```
| | Ease of use | Awesomeness |
| ------------ | ------------- | ------------ |
| **Web PKI** | Very easy | So good |
| **Rest PKI** | Super easy | Much awesome |
| **PKI SDK** | Easy peasy | Wow |
O texto nas células normalmente fica alinhado à esquerda. Para outros alinhamentos:
```md
| Left-aligned | Center-aligned | Right-aligned |
| :--- | :---: | ---: |
| git status | git status | git status |
| git diff | git diff | git diff |
```
| Left-aligned | Center-aligned | Right-aligned |
| :--- | :---: | ---: |
| git status | git status | git status |
| git diff | git diff | git diff |
Veja também:
* [Organizing information with tables - GitHub Help](https://help.github.com/articles/organizing-information-with-tables/)
* Exemplos
* Tabela com a célula superior esquerda em branco: [Web PKI / Introdução](https://docs.lacunasoftware.com/articles/web-pki)
* Tabela com texto centralizado: [Web PKI / Browsers suportados](https://docs.lacunasoftware.com/articles/web-pki/browser-support)
# [Política de Privacidade](https://docs.lacunasoftware.com/articles/welcome/privacy-policy)
* Última alteração: 16 de junho de 2020
* Data efetiva: 16 de junho de 2020
A Lacuna Software Ltda (coletivamente, "Lacuna", "nós", "nos" ou "nosso") respeita a sua privacidade. Os serviços que oferecemos são ferramentas para que organizações e desenvolvedores façam uso de certificação digital e tecnologias correlatas com segurança.
Nossa Política de Privacidade descreve os tipos de Dados Pessoais que coletamos por nossos produtos e serviços (“Serviços”) e por nossa presença online, que inclui nosso site principal, [Lacuna Software](https://lacunasoftware.com/), e serviços e sites que disponibilizamos na Internet, como o [Dropsigner](https://www.dropsigner.com/) (coletivamente, nossos “Sites”). Esta política também descreve como usamos Dados Pessoais, com quem os compartilhamos, quais são seus direitos e opções e como você pode entrar em contato conosco para falar sobre as nossas práticas de privacidade. Esta política não se aplica a sites, produtos ou serviços de terceiros, mesmo que eles estejam vinculados aos nossos Serviços ou Sites, portanto, informe-se com atenção sobre as práticas de privacidade de tais terceiros.
De modo a facilitar referenciação, nossa Política de Privacidade está segmentada por produto e serviço, observando as especificidades de cada um. Na lista abaixo é possível encontrar todos os artigos:
* [Política de Privacidade do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/privacy-policy)
* [Política de Privacidade do Web PKI](https://docs.lacunasoftware.com/articles/web-pki/privacy-policy)
## Perguntas / Comentários sobre privacidade
Se você tiver questões ou sugestões sobre a Política de Privacidade da Lacuna, por favor entre em contato conosco em [suporte@lacunasoftware.com](mailto:suporte@lacunasoftware.com) ou EQN 102/103, Ed. Avenida 102, 2º Andar - Asa Norte, Brasília-DF, Brasil. Telefone: +55 61 3030-5700.
# [Referência rápida do Web PKI](https://docs.lacunasoftware.com/articles/web-pki/quick-reference)
O essencial do Web PKI em uma página, com links para os artigos completos.
## Para que serve
O Web PKI permite que aplicações web usem, por JavaScript, os certificados do computador do usuário: listá-los, ler suas propriedades e seu encoding binário e assinar dados ou um digest pré-calculado com eles.
Seu papel principal é a parte da assinatura feita no cliente: um SDK no servidor prepara os bytes a assinar e monta o arquivo assinado, porque `signData` e `signHash` devolvem só a saída bruta do algoritmo de assinatura. Se você já tem um SDK de criptografia ou escreve o backend por conta própria, o Web PKI sozinho cobre a parte do frontend.
| Alternativa | Quando é a melhor escolha |
| --- | --- |
| [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference) (legado) | Seu backend já usa o Rest PKI em pki.rest: o guia de certificação digital recomenda o Rest PKI com o Web PKI para assinaturas web remotas em qualquer linguagem. |
| [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/quick-reference) | Você prefere não escrever código de assinatura: sua aplicação cria uma sessão de assinatura e o Rest PKI Core hospeda a página de assinatura, em que os usuários assinam pelo Web PKI ou com certificados em nuvem. |
| [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/quick-reference) | Seu backend é .NET: o PKI SDK faz o lado servidor das assinaturas web remotas, com o Web PKI no navegador. |
| [PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference) | Seu backend roda em Windows ou Linux, em praticamente qualquer linguagem: o PKI Express assina no servidor, e seus exemplos usam o Web PKI no navegador. |
| [Signer](https://docs.lacunasoftware.com/articles/signer/quick-reference) | Você precisa de um gerenciador completo de documentos e assinaturas, e não de um componente; o widget de assinatura embutida dele exige uma licença do Web PKI para o domínio da sua página. |
## Instalação
| Linguagem | Pacote | Major atual | Registry |
| --- | --- | --- | --- |
| JavaScript / TypeScript (npm) | `web-pki` | 2 (mais recente: 2.18.0) | https://www.npmjs.com/package/web-pki |
| Tag script (CDN) | `lacuna-web-pki-2.18.0.min.js` | 2 | https://cdn.lacunasoftware.com/libs/web-pki/lacuna-web-pki-2.18.0.min.js |
```html
```
```bash
npm install web-pki --save
```
Com o npm, importe a classe como faz o exemplo em Angular:
```javascript
import LacunaWebPKI from 'web-pki';
```
O Web PKI também precisa dos seus componentes no computador do usuário: quando eles faltam ou estão desatualizados, ou o navegador não é suportado, `init()` leva o usuário à página de instalação, que o devolve à sua página ao terminar.
Os componentes se atualizam sozinhos; atualize regularmente a biblioteca JavaScript na sua aplicação, pois as atualizações são retrocompatíveis.
Veja [Browsers suportados](https://docs.lacunasoftware.com/articles/web-pki/browser-support) e, para redes Windows, a [distribuição via GPO](https://docs.lacunasoftware.com/articles/web-pki/installation-webpki-gpo).
## Configuração e autenticação
O Web PKI roda no navegador do usuário: não há chave de API. Fora do localhost, sua página precisa de uma **licença** vinculada aos seus domínios, passada ao construtor de `LacunaWebPKI` em formato binário ou JSON.
```javascript
var webPkiLicense = 'ASYAanNma...Q==';
var pki = new LacunaWebPKI(webPkiLicense);
```
- A licença não é necessária no localhost (qualquer porta), nem com o Rest PKI em nuvem, inclusive em produção.
- A licença JSON lista `allowedDomains` e `homologDomains` (homologação); estes incluem as faixas de IP privado 10.x, 127.x, 172.16–172.31 e 192.168.x. Use a licença inteira: o campo `signature` sozinho não é uma licença. Veja [Licenciamento](https://docs.lacunasoftware.com/articles/web-pki/licensing).
- Para obter uma licença, [fale com a Lacuna](https://www.lacunasoftware.com/en/home/purchase); as licenças do PKI SDK em geral vêm acompanhadas de uma licença do Web PKI.
Os argumentos de `init()` que um cliente define:
| Argumento | Finalidade | Artigo |
| --- | --- | --- |
| `ready` | Callback chamado quando o componente está pronto | [Primeiros passos](https://docs.lacunasoftware.com/articles/web-pki/get-started) |
| `license` | A licença, quando não foi passada ao construtor | [Licenciamento](https://docs.lacunasoftware.com/articles/web-pki/licensing) |
| `defaultFail` | Callback de falha para toda chamada que não tenha o seu | [Callback de erro padrão](https://docs.lacunasoftware.com/articles/web-pki/default-error-callback) |
| `notInstalled` | Substitui o redirecionamento para a página de instalação | [Componente não instalado](https://docs.lacunasoftware.com/articles/web-pki/customizing-not-installed) |
| `restPkiUrl` | Sua instância do Rest PKI, quando não é a padrão https://pki.rest | [URL do Rest PKI](https://docs.lacunasoftware.com/articles/web-pki/customizing-restpki-url) |
| `requiredApiVersion` | Conjunto de recursos da API de que a página precisa (padrão: API 1.3), evitando atualizações desnecessárias | [Versões da API](https://docs.lacunasoftware.com/articles/web-pki/api-reference/versions) |
| `brand` | Página de instalação personalizada | [Página de instalação personalizada](https://docs.lacunasoftware.com/articles/web-pki/brand) |
| `angularScope`, `ngZone` | Executa os callbacks dentro dos ciclos do AngularJS ou do Angular | [Callbacks e AngularJS](https://docs.lacunasoftware.com/articles/web-pki/angularjs) |
Com o Rest PKI, o Web PKI se comunica diretamente com a instância do Rest PKI, por padrão pela `https://restpki.lacunasoftware.com/`, uma URL alternativa do serviço em nuvem em https://pki.rest, com menos chance de ser bloqueada por firewalls.
## Operações principais
Todos os métodos são assíncronos: devolvem uma `Promise` na qual você registra os callbacks `success()` e `fail()` ([Chamadas assíncronas](https://docs.lacunasoftware.com/articles/web-pki/async)). A [referência da API](https://docs.lacunasoftware.com/articles/web-pki/api-reference) traz todos os métodos, inclusive os que não aparecem aqui, como `sendAuthenticatedRequest()` para [requisições autenticadas](https://docs.lacunasoftware.com/articles/web-pki/auth-request) com o certificado do usuário via SSL/TLS mútuo.
### Inicializar o componente
Crie um `LacunaWebPKI` e chame `init()` antes de qualquer outro método; seu callback `ready` é chamado quando o componente está pronto. Veja [Primeiros passos](https://docs.lacunasoftware.com/articles/web-pki/get-started).
```javascript
var pki = new LacunaWebPKI();
pki.init({
ready: onWebPkiReady,
defaultFail: onWebPkiFail
});
```
### Listar os certificados do usuário
`listCertificates()` devolve os certificados ordenados por `subjectName`, cada um com propriedades como `thumbprint`, `subjectName`, `issuerName` e `validityEnd`; com `selectId`, preenche um elemento select. Veja [Personalizando a listagem de certificados](https://docs.lacunasoftware.com/articles/web-pki/cert-select) e [Propriedades dos certificados](https://docs.lacunasoftware.com/articles/web-pki/cert-properties).
Mostre todos os certificados e [pré-valide o escolhido](https://docs.lacunasoftware.com/articles/web-pki/cert-validation) em vez de usar [filtros](https://docs.lacunasoftware.com/articles/web-pki/filters), que a documentação desaconselha.
```javascript
pki.listCertificates({
selectId: 'certificateSelect',
selectOptionFormatter: getCertText
});
function getCertText(cert) {
return cert.subjectName + ' (issued by ' + cert.issuerName + ')';
}
```
### Ler um certificado
`readCertificate()` devolve o encoding do certificado em Base64, que você envia ao backend quando ele inicia uma assinatura remota. Veja [Primeiros passos](https://docs.lacunasoftware.com/articles/web-pki/get-started).
Passe `{ thumbprint }`: a biblioteca também aceita a string do thumbprint, como faz o exemplo em Java, mas a declaração de tipos TypeScript (`lacuna-web-pki.d.ts`) aceita só o objeto.
```javascript
pki.readCertificate({ thumbprint: selectedCertThumbprint }).success(function (certEncoding) {
// certEncoding contains the certificate's encoding (in Base64)
});
```
### Assinar dados
`signData()` assina bytes em Base64 com a chave privada do certificado escolhido e devolve a assinatura em Base64. Veja [signData e signHash](https://docs.lacunasoftware.com/articles/web-pki/faq/sign-data-vs-hash).
```javascript
pki.signData({
thumbprint: '...', // as returned by the listCertificates method
data: '...', // bytes encoded in Base64 (typically given by server)
digestAlgorithm: '...' // for instance 'SHA-256' (typically given by server)
}).success(function (signature) {
// signature contains the result of the signature operation (encoded in Base64)
});
```
### Assinar um hash vindo do backend
`signHash()` assina o digest que o SDK no servidor devolve ao iniciar uma assinatura remota (PKI Express, PKI SDK, Rest PKI Core); em seguida, o backend conclui a assinatura com o resultado. Veja [Assinaturas web remotas](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/remote).
```javascript
pki.signHash({
thumbprint: formElements.certThumbField.val(),
hash: formElements.toSignHashField.val(),
digestAlgorithm: formElements.digestAlgorithmField.val()
}).success(function (signature) {
// Fill the "signature" field, needed on server-side to complete the signature.
formElements.signatureField.val(signature);
formElements.form.submit();
});
```
### Assinar com o Rest PKI
`signWithRestPki()` recebe o token que o backend obteve do Rest PKI e o thumbprint do certificado; o Web PKI troca os dados da assinatura diretamente com o Rest PKI, e depois o backend conclui a assinatura. Veja [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference).
```javascript
pki.signWithRestPki({
token: formElements.token,
thumbprint: selectedCertThumbprint
}).success(function () {
// Once the operation is completed, we submit the form.
formElements.form.submit();
});
```
### Assinar um PDF local
`signPdf()` assina um PDF no computador do usuário, sem enviar nada a um servidor; escolha o arquivo com `showFileBrowser()`. As assinaturas locais exigem a extensão de assinatura local ([fale com a Lacuna](https://www.lacunasoftware.com/en/home/purchase)) e só funcionam no Windows. Veja [Assinaturas web locais](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/local).
```javascript
pki.signPdf({
fileId: file.id,
output: { mode: 'autoSave', fileNameSuffix: '-signed' },
certificateThumbprint: $('#certificateSelect').val(),
policy: 'basic',
trustArbitrators: [pki.standardTrustArbitrators.pkiBrazil]
}).success(function (result) {
// result.isValid, result.signatureInfo
});
```
## Erros
Uma chamada que falha chama seu callback `fail()`, ou o `defaultFail` definido em `init()`; sem nenhum dos dois, uma exceção é lançada. O callback recebe um `ExceptionModel` com `userMessage` (para o usuário), `code`, `error`, `origin` e `message`. Veja [Callback de erro padrão](https://docs.lacunasoftware.com/articles/web-pki/default-error-callback).
- **Componente ausente ou desatualizado, ou navegador não suportado**: não é um código de erro. `init()` redireciona para a página de instalação, ou chama seu `notInstalled(status, message)` com `NOT_INSTALLED`, `OUTDATED` ou `BROWSER_NOT_SUPPORTED`. Veja [Componente não instalado](https://docs.lacunasoftware.com/articles/web-pki/customizing-not-installed).
- **Códigos de erro**: `code` indica qual erro ocorreu, e [Callback de erro padrão](https://docs.lacunasoftware.com/articles/web-pki/default-error-callback#códigos-de-erro) descreve cada um. Com `license_not_set`, `license_invalid`, `license_expired`, `license_domain_not_allowed` e `license_restricted`, confira a licença com o artigo [Licenciamento](https://docs.lacunasoftware.com/articles/web-pki/licensing); outros códigos comuns são `user_cancelled` (o usuário cancelou a operação) e `certificate_not_found` (o certificado não está no repositório do usuário nem no dispositivo criptográfico).
- **"Could not get pending signature"** com `signWithRestPki()`: o Web PKI não consegue se comunicar com a instância do Rest PKI, o que é comum com instâncias on-premises; verifique primeiro se o computador do usuário consegue acessá-la. Veja [o artigo de solução de problemas](https://docs.lacunasoftware.com/articles/rest-pki/troubleshoot/could-not-get-pending-sig).
TODO(docs): 13 códigos de erro não têm descrição, nem na documentação nem na declaração de tipos da biblioteca: `algorithm_not_supported`, `signed_pdf_to_mark`, `json_error`, `io_error`, `keychain_error`, `keychain_sign_error`, `decode_error`, `csp_invalid_algorithm`, `csp_invalid_provider_type`, `mobile_not_authorized`, `command_decrypt_error`, `blocked_domain` e `invalid_operation`.
## Certificados e credenciais de teste
- Use os certificados da PKI de teste da Lacuna, em [Certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#web-pki); nunca confie nessa PKI em produção.
- Para testar o Web PKI no Windows, você precisa de um certificado no repositório de certificados do Windows: o `certmgr.msc` deve mostrar um na pasta **Pessoal**, com a chave privada. Para usar um certificado de teste, importe o arquivo PKCS #12 dele (senha `1234`) para esse repositório.
- A licença não é necessária no localhost, e servidores de homologação em IPs privados são cobertos pelos `homologDomains` da licença.
- Com o Rest PKI, confie na PKI de teste pelo ID de contexto de segurança informado em Certificados de teste; para assinaturas locais, adicione a raiz de teste, Lacuna Root Test v3, como trust arbitrator `trustedRoot` (só em desenvolvimento). O exemplo em .NET faz isso com uma raiz antiga, a Lacuna Root Test v1, que expirou em janeiro de 2025 e não aceita os certificados de teste atuais.
TODO(docs): como instalar os arquivos PKCS #12 de teste para que o Web PKI os liste no Linux e no macOS.
## Experimente
Rode as demos do Web PKI ou os exemplos na sua linguagem:
**Demonstrações e exemplos**
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Sign a local PDF in the browser with Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureWebController.cs)
- Sign a local file with CAdES in the browser using Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesSignatureWebController.cs)
- List the user's certificates with Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/ListCertJQueryController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ListCertJQueryController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/list-cert-jquery.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/list_cert_jquery.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/list_cert_jquery_controller.rb)
- Read a certificate's details with Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/ReadCertJQueryController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ReadCertJQueryController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/read-cert-jquery.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/read_cert_jquery.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/read_cert_jquery_controller.rb)
- Sign data with RSA in the browser using Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/RSAWebController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/RSAWebController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/rsa-web.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/rsa_web.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/rsa_web_controller.rb)
# [Lacuna Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
O componente Web PKI possibilita que páginas web interajam com certificados digitais através de código javascript.
> **Dica**
>
> O Web PKI faz parte da nossa linha de soluções para certificação digital. Recomendamos que você leia o artigo
> [Certificação Digital](https://docs.lacunasoftware.com/articles/pki-guide) para ter certeza de que esse é o produto mais indicado para o seu caso.
As principais funcionalidades são:
* Recuperar os certificados disponíveis (A1, A3, etc)
* Obter os atributos públicos do certificado (Nome, e-mail, etc)
* Ler a codificação binária de um certificado
* Assinar dados (ou um hash pré-computado) com um certificado
O principal objetivo do Web PKI é realizar o processamento client-side necessário em operações com certificados digitais. Um exemplo de operação seria uma
[assinatura digital remota](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/remote) utilizando um SDK (Software Development Kit) no servidor:
| | *Client-side* (Lacuna Web PKI) | *Server-side* (SDK no servidor) |
| - | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1 | Obter o *encoding* do certificado e enviar ao servidor | Preparar a assinatura, produzindo os bytes que devem ser utilizados como entrada para o algoritmo de assinatura ("to-sign-bytes" ou "signed attributes") |
| 2 | Assinar os "to-sign-bytes" com a chave de um certificado digital e enviar o resultado ao servidor | Montar o pacote de assinatura -- seja um PDF assinado (PAdES), um envelope CMS (CAdES) um XML assinado (XAdES) -- dependendo do tipo de assinatura utilizada |
> **Observação**
>
> Se você não possui um SDK para realizar o processamento no servidor, você pode usar o [Lacuna PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk).
> [Fale conosco](https://www.lacunasoftware.com/pt/home/purchase) para obter informações de licenciamento.
Veja o artigo [Primeiros passos](https://docs.lacunasoftware.com/articles/web-pki/get-started) para começar a utilizar o Web PKI.
# [Web PKI - Perguntas frequentes (FAQ)](https://docs.lacunasoftware.com/articles/web-pki/faq)
Aqui estão reunidas as repostas para as dúvidas mais frequentes sobre o uso do [Web PKI](https://docs.lacunasoftware.com/articles/web-pki).
> **Dica**
>
> Essa FAQ é destinada a **desenvolvedores de software**. Se você é um usuário final
> tendo problemas para utilizar algum de nossos produtos, por favor visite a nossa [Central de Suporte](http://lacuna.help/)
Perguntas:
* [Qual é a diferença entre as funções signData e signHash?](https://docs.lacunasoftware.com/articles/web-pki/faq/sign-data-vs-hash)
* [Qual é o formato da assinatura retornada pelas funções signData e signHash?](https://docs.lacunasoftware.com/articles/web-pki/faq/sign-data-output)
* [Como posso realizar uma assinatura digital utilizando o Web PKI?](https://docs.lacunasoftware.com/articles/web-pki/faq/digital-signature)
* [O Web PKI suporta conexões SSL usando o certificado do usuário? (ou Como faço para transmitir uma NFe Para a Receita/SEFAZ/prefeitura?)](https://docs.lacunasoftware.com/articles/web-pki/faq/ssl)
# [Como posso realizar uma assinatura digital utilizando o Web PKI?](https://docs.lacunasoftware.com/articles/web-pki/faq/digital-signature)
Veja o artigo [Assinaturas web](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures).
**Demonstrações e exemplos**
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Sign a local PDF in the browser with Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureWebController.cs)
- Sign a local file with CAdES in the browser using Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesSignatureWebController.cs)
# [Qual é o formato da assinatura retornada pelas funções signData e signHash?](https://docs.lacunasoftware.com/articles/web-pki/faq/sign-data-output)
As funções `signData` e `signHash` realizam um ***algoritmo*** *de assinatura digital* utilizando a chave privada de um
dos certificados do usuário. Sendo assim, o formato retornado é a saída do algoritmo de assinatura codificada em Base64.
No caso mais comum, que é do algoritmo RSA com chave de 2048 bits, a saída tem 256 bytes (344 caracteres em Base64).
Essa dúvida geralmente ocorre devido à confusão entre uma *assinatura digital*, por exemplo CMS/CAdES, XmlDSig/XAdeS ou
PAdES, e um ***algoritmo*** *de assinatura*, como o RSA. Geralmente, o algoritmo de assinatura é uma parte do processo
de realizar uma assinatura digital. As funções `signData` e `signHash` realizam apenas o algoritrmo, não a assinatura
inteira.
Por favor veja a pergunta [Como posso realizar uma assinatura digital utilizando o Web PKI?](https://docs.lacunasoftware.com/articles/web-pki/faq/digital-signature) para
informações sobre como utilizar o Web PKI para realizar assinaturas digitais.
# [Qual é a diferença entre as funções signData e signHash?](https://docs.lacunasoftware.com/articles/web-pki/faq/sign-data-vs-hash)
Ambas funções -- `signData` e `signHash` -- realizam um algoritmo de assinatura digital utilizando a chave privada de
um dos certificados presentes na máquina do usuário. Para entender a diferença entre as duas funções, precisamos falar
rapidamente sobre os detalhes de um algoritmo de assinatura.
> **Observação**
>
> O termo ***algoritmo*** *de assinatura digital* não deve ser confundido com *assinatura digital*. Leia mais sobre
> esse assunto no artigo [Assinaturas digitais](https://docs.lacunasoftware.com/articles/pki-guide/signatures).
O algoritmo RSA, em linhas gerais, tem 4 etapas:
1. Cálculo do hash dos bytes a serem assinados
1. Codificação da estrutura [DigestInfo](https://tools.ietf.org/html/rfc2313#section-10.1.2), que contém o hash e o OID do algoritmo de hash usado para produzi-lo
1. *Padding* do DigestInfo codificado
1. Operação matemática sobre DigestInfo codificado e *padded*
> **Observação**
>
> O cálculo de hash realizado na primeira etapa geralmente **não é sobre o documento sendo assinado**. Nos esquemas de
> assinatura CMS/CAdES, PAdES e XmlDSig/XAdES, os bytes usados como entrada para o algoritmo de assinatura correspondem
> à codificação dos "atributos assinados", uma estrutura que contém diversos dados, entre eles o hash do documento.
A função `signData` recebe como entrada o *to-sign-data* e realiza sobre ele todas as etapas, enquanto que a função
`signHash` recebe como entrada o *to-sign-hash*, isto é, o hash pré-computado do *to-sign-data*, e realiza sobre ele
apenas da segunda etapa em diante.
Ambas funções podem ser usadas na implementação de [assinaturas web remotas](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/remote)
independentemente do esquema de assinatura (CAdES, PAdES, etc) sendo utilizado. A escolha da função a ser utilizada
depende do *output* fornecido pelo código que executa em backend ao final do "preparo" da assinatura remota. Se for
produzido um *to-sign-data*, utilize a função `signData`. Se for produzido um *to-sign-hash*, utilize a função `signHash`.
**Demonstrações e exemplos**
- Sign data with RSA in the browser using Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/RSAWebController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/RSAWebController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/rsa-web.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/rsa_web.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/rsa_web_controller.rb)
# [O Web PKI suporta conexões SSL usando o certificado do usuário?](https://docs.lacunasoftware.com/articles/web-pki/faq/ssl)
Sim. Desde a [versão 1.4 da API](https://docs.lacunasoftware.com/articles/web-pki/api-reference/versions#v1-4), o método `sendAuthenticatedRequest()` envia requisições web
autenticadas com o certificado do usuário através de SSL/TLS mútuo, a partir do computador do usuário. Veja [Requisição autenticada](https://docs.lacunasoftware.com/articles/web-pki/auth-request).
Para transmitir uma NFe, porém, em geral não é preciso usar o certificado do usuário, como explicado abaixo.
## (ou Como faço para transmitir uma NFe Para a Receita/SEFAZ/prefeitura?)
Atualmente, é comum utilizar um mesmo certificado para emitir uma nota fiscal eletrônica (NFe) e transmiti-la ao órgão
competente (a transmissão é feita via SSL com autenticação mútua). A predominância dessa arquitetura advém do fato de
que os primeiros produtos de assinatura de NFe eram, em sua grande maioria, aplicações desktop. Em um sistema web, é
preciso lidar com o problema de o certificado do cliente não estar disponível no backend.
Para resolver o problema da assinatura da NFe no browser, o componente Web PKI se adequa perfeitamente (veja o
[exemplo online](https://www.lacunasoftware.com/en/DemoRestPKI/nfeSignature)). Já no caso da transmissão, não é
necessário utilizar o mesmo certificado utilizado para emitir a NFe. O padrão nacional permite que seja utilizado para
transmissão um certificado emitido para uma empresa diferente da empresa que emitiu a NFe.
Isso possibilita que **a transmissão seja feita no backend utilizando um certificado A1 emitido em nome da empresa que
oferece o serviço de assinatura de NFe na web (sua empresa), ao invés de usar o certificado da empresa emissora da NFe
(seu cliente)**. Assim, evita-se o problema de transmitir a NFe com um certificado disponível apenas no frontend.
# [Web PKI API](https://docs.lacunasoftware.com/articles/web-pki/api-reference)
## Guia da API
Para documentação detalhada de cada método, acesse o [Guia da API](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html).
## Veja também
[Versões da API](https://docs.lacunasoftware.com/articles/web-pki/api-reference/versions)
# [Versões da API](https://docs.lacunasoftware.com/articles/web-pki/api-reference/versions)
O Web PKI oferece a possibilidade de o desenvolvedor definir qual a versão da API deseja exigir, baseando-se no conjunto de funcionalidades que deseja utilizar.
Para isso, utilize o parâmetro `requiredApiVersion` no método [`init()`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#init):
```js
pki.init({
ready: onWebPkiReady,
requiredApiVersion: pki.apiVersions.v1_2
});
```
No exemplo acima, definimos que iremos utilizar funcionalidades introduzidas na [API 1.2](https://docs.lacunasoftware.com/articles/web-pki/api-reference/versions#v1-2). Desta forma, a atualização dos componentes do Web PKI só será exigida aos usuários com versões inferiores às definidas pela [API 1.2](https://docs.lacunasoftware.com/articles/web-pki/api-reference/versions#v1-2).
Nenhuma atualização desnecessária será exigida de usuários com versões suficientes, ainda que não sejam a mais recente.
Se o parâmetro não for passado, o padrão é exigir a [API 1.3](https://docs.lacunasoftware.com/articles/web-pki/api-reference/versions#v1-3).
## Histórico de versões da API
### 1.8.2 (2023-11-23)
Desde lib [2.16.3](https://docs.lacunasoftware.com/articles/web-pki/update)
- Melhoria no método [`importCertificate`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#importcertificate) suportando múltiplas chamadas
### 1.8.1 (2022-12-17)
- Adiciona métodos [encrypt](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#encrypt) e [decrypt](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#decrypt)
- Adiciona Extended Key Usage info ao [CertificateModel](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.certificatemodel.html)
- Adiciona o parâmetro `nonExportableKey` ao método [generateSoftwareRsaKeyPair](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#generatesoftwarersakeypair)
### 1.7.2 (2022-07-03)
- Adiciona suporte a respostas sem limite de tamanho do App
- Adiciona campos de PKI internacionais no [CertificateModel](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.certificatemodel.html): [`PkiArgentinaModel`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.pkiargentinamodel.html), [`PkiEcuadorModel`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.pkiecuadormodel.html), [`PkiParaguayModel`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.pkiparaguaymodel.html), [`PkiPeruModel`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.pkiperumodel.html)
- Adiciona campos no [CertificateModel](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.certificatemodel.html): `certificatePolicies`, `subjectDN`, `issuerDN`
### 1.6.1 (2020-05-23)
- Adiciona comando [`downloadToFolder`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#downloadtofolder)* com suporte forçado a TLS 1.2
### 1.6 (2019-10-13)
- Adiciona métodos [`keySignData`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#keysigndata) e [`keySignHash`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#keysignhash) para assinatura com Id de chave privada gerada.
- Adiciona o parâmetro `privateKeyId` ao resultado da geração de par de chaves [`GenerateKeyPairResponse`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.generatekeypairresponse.html).
- Corrige bug na resposta do método [`sendAuthenticatedRequest`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#sendauthenticatedrequest) quando o *payload* retornado não possui a propriedade `Content-Length` no *Header*.
### 1.5.2 (2019-07-19)
- Adiciona opção de retorno do conteúdo do documento assinado nos comandos de assinatura local sem limite de 1MB: [`returnContent`](https://docs.lacunasoftware.com/content/typedocs/web-pki/enums/_lacuna_web_pki_d_.lacunawebpki.outputmodes.html#returncontent)
- Adiciona nível de validação do certificado do assinante nos comandos de assinatura local: [`CertificateValidationLevels`](https://docs.lacunasoftware.com/content/typedocs/web-pki/enums/_lacuna_web_pki_d_.lacunawebpki.certificatevalidationlevels.html)
- Adiciona controle de direção vertical e horizontal na representação visual da assinatura PAdES (PDF): [`PadesVisualAutoPositioning`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.padesvisualautopositioning.html)
- Adiciona controle do ciclo de vida da aplicação nativa no método `init`: [`useDomainNativePool`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#init)
### 1.5 (2018-11-27)
- Adiciona comando mais eficiente de assinatura de lote: [`signHashBatch`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#signhashbatch)
- Adiciona licença v3
- Adiciona integração com _mobile_
### 1.4.1 (2018-06-15)
- Corrige *bug* de *buffer* vazio no comando [`sendAuthenticatedRequest`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#sendauthenticatedrequest)*
### 1.4 (2018-02-23)
- Adiciona funcionalidades de assinatura local de XML:
- [`signFullXml`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#signfullxml)*
- [`signXmlElement`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#signxmlelement)*
- [`openXmlSignature`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#openxmlsignature)*
- Adiciona funcionalidades de requisição Web autenticada:
- [`sendAuthenticatedRequest`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#sendauthenticatedrequest)*
- Adiciona mensagem de erro de usuário no objeto de exceção:
- [`userMessage`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.exceptionmodel.html#usermessage)
### 1.3 (2017-11-10)
- Adiciona gerenciador de erro com objeto de exceção mais detalhado:
- [`fail`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.promise.html#fail)
### 1.2 (2017-06-19)
- Adiciona funcionalidades para emissão de certificado digital em repositório local e via PKCS#11:
- [`generateSoftwareRsaKeyPair`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#generatesoftwarersakeypair)
- [`importCertificate`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#importcertificate)
- [`listTokens`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#listtokens)
- [`generateTokenRsaKeyPair`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#generatetokenrsakeypair)
- [`importTokenCertificate`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#importtokencertificate)
### 1.1 (2016-08-19)
- Adiciona licença v2
- Adiciona funcionalidades para assinatura local:
- [`showFileBrowser`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#showfilebrowser)*
- [`openFile`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#openfile)*
- Adiciona funcionalidades de assinatura local PAdES:
- [`signPdf`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#signpdf)*
- [`openPades`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#openpades)*
- Adiciona funcionalidades de assinatura local CAdES:
- [`signCades`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#signcades)*
- [`openCades`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#opencades)*
### 1.0 (2015-04-28)
- Adiciona funcionalidades básicas:
- [`init`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#init)
- [`listCertificates`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#listcertificates)
- [`readCertificate`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#readcertificate)
- [`signHash`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#signhash)
- [`signData`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#signdata)
- [`redirectToInstallPage`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#redirecttoinstallpage)
- Adiciona funcionalidade para assinatura em lote:
- [`preauthorizeSignatures`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#preauthorizesignatures)
- Adiciona funcionalidade de download e seleção de diretório:
- [`showFolderBrowser`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#showfolderbrowser)
- [`downloadToFolder`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#downloadtofolder)
- [`openFolder`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#openfolder)
- Adiciona integração de assinatura com RestPKI:
- [`signWithRestPki`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#signwithrestpki)
* Métodos suportados apenas em Windows. Para mais informações veja o artigo [Assinaturas web](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures).
# [privacy-policy](https://docs.lacunasoftware.com/articles/web-pki/privacy-policy)
> **Observação**
>
> Revisamos nossas políticas para garantir que estamos em conformidade com o Regulamento Geral de Proteção de Dados da União Europeia
# Web PKI - Política de Privacidade
* Última alteração: 16 de junho de 2020
* Data efetiva: 16 de junho de 2020
Esta Política de Privacidade se aplica à extensão [Web PKI](https://get.webpkiplugin.com/Help?brand=&jslib=&returnUrl=#) (o “Plugin”, a “Extensão”) pertencente e operado pela Lacuna Software Ltda (coletivamente, "Lacuna", "nós", "nos" ou "nosso"). Esta Política de Privacidade descreve como a Lacuna coleta, usa, compartilha e protege as informações pessoais que você fornece, bem como as informações sobre os usuários finais de nossos clientes que processamos em nome de nossos clientes. Também descreve suas opções em relação ao uso, acesso, correção e exclusão de suas informações pessoais.
## Como coletamos informações
Coletamos as seguintes informações pessoais:
### Informação que você nos fornece sobre você
Quando você se inscreve nos Serviços, solicitamos informações como
* Informações de contato, tal como nome, endereço de e-mail, endereço para correspondência ou número de telefone;
* Identificadores exclusivos, como nome de usuário, número da conta ou senha;
* Endereço IP.
Se você não fornecer essas informações, não poderemos fornecer os Serviços a você.
### Informação que você fornece sobre seus usuários e contatos
Para que possamos efetivar a prestação de nossos serviços, você nos fornece informações que podem identificar seus usuários finais ou funcionários.
Essas informações incluem:
* Certificado digital;
* Endereço IP;
* Hash de documento.
Você também pode nos fornecer informações pessoais sobre seus funcionários como Nome e endereço de e-mail onde podemos enviar recibo de suas compras. Quando você nos fornece informações pessoais sobre seus funcionários, essas informações serão
usadas apenas pelo motivo específico pelo qual foram fornecidas. Se você acredita que um de seus funcionários nos forneceu suas informações pessoais e gostaria de solicitar que elas sejam removidas de nosso banco de dados, entre em contato conosco pelas informações de contato abaixo.
### Informação coletada automaticamente
Quando você utiliza nossos Serviços, nós utilizamos cookies e tecnologias similares para coletar informações sobre sua localização, e suas atividades em nosso Plugin, informações sobre o seu dispositivo e informações de registro. Isso é descrito com mais detalhes abaixo. Estes cookies e tecnologias similares coletam endereço de protocolo de Internet (IP) anonimizado, tipo do browser, serviço de provedor de internet (ISP), páginas de saída/referências, sistema operacional, data/hora, e/ou dados do fluxo de cliques.
Lacuna permite que o Google Analytics e o Snowplow Analytics usem essas tecnologias para analisar tendências, administrar o Plugin, rastrear os movimentos dos usuários no Plugin e coletar informações demográficas sobre nossa base de usuários como um todo para fins de análise.
Você pode controlar o uso de cookies no nível do navegador individual, mas se você escolher desabilitar cookies, isto pode limitar seu uso de determinados recursos ou funções em nosso Plugin ou serviço. Nós não podemos usar ou permitir o uso de cookies ou tecnologias similares em conexão com os dados de seus clientes.
*Google Analytics:*
Nós usamos uma tela chamada "Google Analytics" para coletar algumas informações que listamos acima sobre o uso de seus Serviços. Usamos as informações que obtemos do Google Analytics para aprimorar os Serviços prestados. Para coletar essas informações, o Google Analytics pode definir cookies no seu navegador ou dispositivo móvel ou ler cookies que já estão lá. O Google Analytics também pode receber informações sobre aplicativos que você baixou, que são parceiros do Google. Não combinamos as informações coletadas pelo uso do Google Analytics com informações de identificação pessoal. A capacidade do Google de usar e compartilhar informações coletadas pelo Google Analytics sobre suas visitas aos Serviços de outro aplicativo que seja parceiro do Google é restrita pelos Termos de Uso do Google Analytics e pela Política de Privacidade do Google. Por favor revise isto e veja [https://policies.google.com/technologies/partner-sites](https://policies.google.com/technologies/partner-sites) para informações sobre como o Google usa as informações fornecidas pelo Google Analytics e como você pode controlar as informações fornecidas para o Google. Para impedir que seus dados sejam usados pelo Google Analytics, você pode fazer o download do complemento do navegador de desativação do Google Analytics para o Google Analytics, que pode ser encontrado aqui.
## Finalidade e base jurídica do processamento
Usamos as informações sobre você para fornecer os serviços e para melhorá-los. Utilizamos informações sobre seus clientes ou usuários finais apenas para fornecer os serviços. Na medida em que a lei de proteção de dados da EU se aplica a esses usos de suas informações, agimos como o "controlador de dados". Na medida em que a lei de proteção de dados da UE se aplica ao processamento de dados de seus clientes ou usuários finais, você atua como "controlador de dados" e nós como "processador de dados".
*Para executar os Serviços:* Usamos suas informações para identificá-lo, processar seu pagamento, fornecer suporte e assistência técnica; e comunicar com você sobre os serviços por e-mail. Por exemplo, entraremos em contato para notificá-lo sobre qualquer problema com os Serviços ou sobre qualquer atividade suspeita em sua conta. A base legal para esses usos na execução do contrato com você.
*Para otimizar ou melhorar os serviços:* Usamos suas informações para autenticar você, evitar fraudes e abusos de nossos serviços e realizar pesquisas. A base legal para esses usos é nosso interesse legítimo na melhoria e otimização de nossas ofertas de serviços e em garantir a segurança de nossos serviços, e aplicamos safeguards adequadas para proteger suas informações (veja [section Security](https://docs.lacunasoftware.com/articles/web-pki/privacy-policy#security)).
## Como nós compartilhamos sua informação
Compartilhamos informações com os provedores de serviços, conforme exigido por lei, e em conexão com a proteção e aplicação de nossos direitos legais e contratuais.
*Com provedores de serviços:* Compartilhamos suas informações com terceiros que prestam serviços em nosso nome para ajudar em nossas atividades comerciais. Isto inclui: processamento de pagamento, prestação de serviço ao cliente, enviando comunicação de marketing (este não está incluído informações sobre seu cliente), conduzindo pesquisas e análises e fornecendo infraestrutura de computação em nuvem. A base legal para o compartilhamento dessas informações é o nosso interesse legítimo em fornecer nossos Serviços com eficiência e implementamos medidas para proteger suas informações.
*Com autoridades públicas ou autoridades policiais:* Em determinadas situações, a Lacuna pode ser obrigada a divulgar dados pessoais em resposta a solicitações legais de autoridades públicas, inclusive para atender aos requisitos de segurança nacional ou de aplicação da lei. Também podemos divulgar suas informações pessoais conforme exigido por lei, como para cumprir uma intimação ou outro processo legal, quando acreditarmos de boa fé que a divulgação é necessária para proteger nossos direitos, quando acreditarmos que há uma violação de nossos Termos de Serviço. Serviço (consulte os Termos de Serviço Web PKI), proteja sua segurança ou a segurança de outras pessoas, investigue fraudes ou responda a uma solicitação do governo. Se a Lacuna estiver envolvida em uma fusão, aquisição ou venda de todos ou parte de seus ativos, você será notificado por e-mail e / ou um aviso em destaque em nosso site sobre qualquer alteração de propriedade, uso de suas informações pessoais e escolhas que você possa ter em relação às suas informações pessoais. Não vendemos, alugamos ou compartilhamos informações pessoais com terceiros sem o seu consentimento prévio. A base legal para isso é nosso interesse legítimo em proteger nossos direitos legais e os de terceiros, o cumprimento de obrigações legais e nosso interesse legítimo em cumprir obrigações legais impostas por autoridades legais fora da EU.
*Prevenir fraudes e abusos dos Serviços:* Compartilharemos informações para prevenir ou detectar fraudes ou solucionar problemas técnicos e, se acharmos necessário investigar, impedir ou tomar medidas em relação a situações que envolvam abuso da infraestrutura dos Serviços. A base legal para isso é o nosso interesse legítimo em manter a segurança de nossos Serviços.
Também processamos e compartilhamos informações de maneira agregada e não identificada, onde as informações são compartilhadas como parte de um relatório estatístico e não contêm informações pessoais.
## Segurança
A segurança de suas informações pessoais é importante para nós. Implementamos medidas adequadas para proteger as informações pessoais enviadas a nós, durante a transmissão e depois que elas são recebidas. A Lacuna Software Ltda toma medidas para garantir que todo o código-fonte, arquivos e dados permaneçam privados e confidenciais. Devido à natureza sensível do código-fonte, levamos isso muito a sério e o tornamos nossa principal preocupação para todos os clientes. Restringimos o acesso às informações pessoais aos funcionários, contratados e agentes da Lacuna que precisam conhecer essas informações para operar, desenvolver ou melhorar nosso serviço. Esses indivíduos estão sujeitos a obrigações de confidencialidade e podem estar sujeitos a disciplina, incluindo rescisão e processo criminal, se não cumprirem essas obrigações. Se você tiver alguma dúvida sobre a segurança de suas informações pessoais, entre em contato conosco através das informações de contato abaixo.
## Direitos com relação às suas informações
Mediante solicitação, a Lacuna fornecerá informações sobre a retenção ou não de suas informações pessoais. Você pode acessar, corrigir, obter uma cópia ou solicitar a exclusão de suas informações pessoais fazendo login em sua conta ou entrando em contato conosco através das informações de contato abaixo. Responderemos à sua solicitação dentro de um prazo razoável. Em certas circunstâncias, podemos ser obrigados por lei a reter suas informações pessoais, ou talvez precisemos retê-las para continuar prestando um serviço.
A Lacuna Software Ltda reconhece que você tem o direito de acessar suas informações pessoais. A Lacuna Software Ltda não tem relacionamento direto com seus usuários finais cujos dados pessoais são processados. Um indivíduo que busca acessar ou procura corrigir, alterar ou excluir dados imprecisos deve encaminhar sua consulta ao Cliente da Lacuna Software Ltda (o controlador de dados). Se solicitado remover dados, responderemos em um prazo razoável. Em certas circunstâncias, podemos ser obrigados por lei a reter suas informações pessoais, ou talvez precisemos retê-las para continuar prestando um serviço.
## Suas escolhas com relação à informação
Comunicação promocional por email: Você pode se inscrever para receber email, boletim informativo ou outras comunicações nossas. Se você deseja interromper o recebimento dessas informações, atualize suas preferências de email usando o link "Cancelar inscrição" encontrado nos emails que enviamos para você ou entrando em contato conosco pelas informações de contato abaixo.
Google Analytics: veja [https://policies.google.com/technologies/partner-sites](https://policies.google.com/technologies/partner-sites) para informações sobre como o Google usa as informações fornecidas pelo Google Analytics e como você pode controlar as informações fornecidas pelo Google. Para impedir que seus dados sejam usados pelo Google Analytics, você pode fazer o download do complemento do navegador de desativação do Google Analytics que pode ser encontrado [aqui](https://tools.google.com/dlpage/gaoptout/).
## Retenção de dados
Podemos reter suas informações enquanto sua conta estiver ativa ou conforme necessário para fornecer serviços, cumprir nossas obrigações legais, resolver disputas e fazer cumprir nossos acordos. Em certas circunstâncias, podemos ser obrigados por lei a reter suas informações pessoais, ou talvez precisemos retê-las para continuar prestando um serviço.
Mesmo se você excluir sua conta, lembre-se de que a exclusão por nossos fornecedores terceirizados pode não ser imediata e que as informações excluídas podem persistir em cópias de segurança por um período de tempo razoável.
## Política em relação às crianças
Os Serviços não são direcionados a crianças menores de 16 anos. Não coletamos intencionalmente Informações Pessoais de crianças menores de 16 anos. Se você tem menos de 16 anos, não use os Serviços e não forneça nenhuma informação pessoal para nós. Se você perceber que uma criança menor de 16 anos nos forneceu informações pessoais sem o seu consentimento, entre em contato conosco em [suporte@lacunasoftware.com](mailto: suporte@lacunasoftware.com). Se você é residente na Califórnia com menos de 18 anos e deseja remover o conteúdo disponível publicamente, entre em contato conosco em [suporte@lacunasoftware.com](mailto: suporte@lacunasoftware.com) com a linha de assunto "Borracha da Califórnia".
## Transferências de informações que não são do EEE
Na medida em que informações pessoais são transferidas para os Estados Unidos de outros países da Área Econômica Européia e da Suíça, incluindo informações pessoais que são transferidas do Canadá, empregamos safeguard semelhantes, como selecionar cuidadosamente fornecedores de terceiros, garantindo que as informações pessoais usadas e divulgado apenas conforme estabelecido nesta política de privacidade e protegido por safeguard de segurança apropriadas.
No entanto, em conexão com essas transferências de dados, suas informações pessoais podem estar sujeitas a leis de privacidade que podem não fornecer a mesma proteção que seu país de residência. Por exemplo, entidades governamentais em outros países podem ter certos direitos para acessar suas informações pessoais. Ao usar este plugin ou nosso Serviço relacionado a este aviso de privacidade, você concorda com a transferência de suas informações pessoais.
## Navegador Do Not Track
Os Serviços honram e aplicam as preferências Do Not Track. Do Not Track (DNT) é uma preferência de privacidade que você pode definir no seu navegador da web para indicar que não deseja que determinadas informações sobre as visitas às suas páginas sejam coletadas nos sites quando você não interagiu com esse serviço na página. Para todos os detalhes, incluindo como ativar o Do Not Track, visite [donottrack.us] (http://donottrack.us/).
## Mudanças para esta Política de Privacidade
Podemos atualizar esta Política de Privacidade para refletir alterações em nossas práticas de informações. Se fizermos alguma alteração relevante, notificaremos você por e-mail (enviado para o endereço de e-mail especificado em sua conta) ou por meio de um aviso neste site antes da efetivação da alteração. Recomendamos que você reveja periodicamente esta página para obter as informações mais recentes sobre nossas práticas de privacidade.
## Perguntas / Comentários sobre privacidade
Se você tiver questões ou sugestões sobre a Política de Privacidade da Lacuna, por favor entre em contato conosco em [suporte@lacunasoftware.com](mailto:suporte@lacunasoftware.com) ou EQN 102/103, Ed. Avenida 102, 2º Andar - Asa Norte, Brasília-DF, Brasil. Telefone: +55 61 3030-5700.
# [Callbacks e AngularJS](https://docs.lacunasoftware.com/articles/web-pki/angularjs)
Se a sua página utilizar o framework AngularJS, você deve passar uma referência para o `$scope` do seu controller ou serviço ao chamar o método `init()`:
```javascript
pki.init({
ready: onWebPkiReady,
angularScope: $scope,
...
});
```
Se você não fizer isso, seus callbacks serão chamados fora dos "ciclos" do AngularJS, o que pode ocasionar que mudanças ao `$scope` não sejam refletidas na view.
# Angular2+
Se a sua página utilizar o framework Angular2+, você deve passar uma referência para o `NgZone` do seu componente ou serviço ao chamar o método `init()`:
```javascript
pki.init({
ready: onWebPkiReady,
ngZone: this.ngZone,
...
});
```
# [Chamadas assíncronas](https://docs.lacunasoftware.com/articles/web-pki/async)
Vejamos novamente nosso exemplo para obter os certificados disponíveis:
```javascript
pki.listCertificates().success(function (certs) {
// fazer alguma coisa com o array "certs"
});
```
O código acima é um exemplo de uma **chamada assíncrona**. O método [`listCertificates()`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#listcertificates) não pode fazer imediatamente todo o procedimento necessário e retornar o
resultado. Ele precisa de tempo para realizar essa operação. Nós *poderíamos* bloquear a execução até que a operação fosse concluída, mas isso é uma má-pratica em
javascript e poderia causar sérios problemas, como a página web travar e o browser sugerir ao usuário que a página deveria ser fechada. Ao invés disso, o método
imediatamente retorna uma **promessa** (uma instância da classe [`Promise`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.promise.html).
Uma promessa é um objeto através do qual você pode registrar callbacks que serão chamados quando a promessa for cumprida, isso é, quando a operação for concluída.
Você deve sempre registrar um callback para quando a operação for concluída com sucesso e, opcionalmente, pode registrar um outro callback para quando ocorrer um
erro durante a operação. Todos os métodos da classe [`Promise`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.promise.html) são encadeáveis, ou seja, você pode fazer o seguinte:
```javascript
pki.listCertificates().success(function (certs) {
// do something with the "certs" array
}).fail(function (ex) {
alert('An error has occurred while retrieving the certificates: ' + ex.userMessage);
});
```
O callback de sucesso sempre recebe um único argumento contendo o resultado da operação. Entretanto, o tipo (string, array ou objeto) e seu significado dependem
do método que retornou a promessa. Você deve verificar a documentação de cada método para obter essas informações.
Para mais informações sobre os parâmetros do objeto da exceção, veja a documentação do método [Promise.fail()](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.promise.html#fail) e [ExceptionModel](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.exceptionmodel.html).
> **Observação**
>
> Todos os métodos da classe [`LacunaWebPKI`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html) são assíncronos, com raras exceções.
# [Requisição Autenticada](https://docs.lacunasoftware.com/articles/web-pki/auth-request)
O Web PKI oferece a funcionalidade de envio de requisições Web autenticadas com o certificado digital do usuário através de SSL/TLS mútuo.
Essa operação tem grande utilidade na comunicação com serviços da Receita Federal, SEFAZ ou prefeituras.
## NFe - Nota Fiscal eletrônica
Outra área na qual a requisição autenticada se faz necessária é a de serviços envolvendo notas fiscais eletrônicas (NFe).
No cenário de transmissão da NFe, o padrão nacional permite que seja utilizado, na autenticação, um certificado de uma empresa diferente da que emitiu a NFe.
Isso possibilita que a transmissão seja feita no *backend* utilizando um certificado A1 emitido em nome da empresa que oferece o serviço de assinatura de NFe na web, por exemplo.
No entanto, existem outros serviços, e.g. consultas e manifestações, nos quais se faz necessária a autenticação apenas com o certificado da empresa emissora da NFe.
Para tais casos, a requisição autenticada com o Web PKI permite a implementação das comunicações à partir do computador do usuário, respeitando ideais de segurança, tanto possibilitando a utilização de certificados em dispositivos criptográficos, A3, tanto evitando a necessidade do envio de certificados A1, deixando-os sob a guarda de terceiros.
## Exemplos
No exemplo abaixo, demonstramos uma simples requisição autenticada que verifica o *status* operacional do serviço SEFAZ-RS em homologação.
```js
// Definicao dos parametros do request
var request = {
// URL do servico
url: 'https://nfe-homologacao.svrs.rs.gov.br/ws/NfeStatusServico/NfeStatusServico4.asmx',
method: 'post',
headers: {
'Content-Type': 'application/soap+xml; charset=utf-8'
},
// Envelope SOAP da requisicao
body: '2 53 STATUS '
};
// Define o certificado do usuario para autenticar a requisicao
request.certificateThumbprint = $('#certificateSelect').val();
// Envia a requisicao
pki.sendAuthenticatedRequest(request).success(function (response) {
// Usa a resposta "response"
});
```
## Veja também
* Exemplo funcional no JSFiddle: [Exemplo de envio de requisição autenticada](https://jsfiddle.net/LacunaSoftware/3cv5xjov/)
* [Documentação do método `sendAuthenticatedRequest()`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#sendauthenticatedrequest) (disponível desde a [versão 1.4 da API](https://docs.lacunasoftware.com/articles/web-pki/api-reference/versions#v1-4))
# [Utilizando uma página de instalação personalizada](https://docs.lacunasoftware.com/articles/web-pki/brand)
```javascript
pki.init({
ready: onWebPkiReady,
brand: 'YourBrand'
});
```
# [Browsers suportados](https://docs.lacunasoftware.com/articles/web-pki/browser-support)
Atualmente, os seguintes browsers são suportados:
|  |  |  |  |
|:---------------------------------------------:|:------------------------------------------------:|:-------------------------------------:|:-----------------------------------------:|
| Chrome Versão 44+ | Firefox Versão 50+ | IE9+ | Microsoft Edge Versão 42+ |
| Em Windows, Linux e Mac | Em Windows, Linux e Mac | Em Windows | Em Windows |
Versões dos sistemas suportados:
* Windows 7 SP2 ou superior\*
* Linux (distribuições baseadas em Debian, RedHat e Slackware)
* Mac OS X 10.12+
> **Observação**
>
> \*Windows XP e Vista: **não há suporte oficial, USE POR SUA PRÓPRIA CONTA E RISCO**, uma vez que estes sistemas estão **descontinuados** pela Microsoft. Para funcionamento, o sistema deve estar em sua versão mais atualizada possível.
# [Propriedades dos certificados](https://docs.lacunasoftware.com/articles/web-pki/cert-properties)
Ao chamar a função `listCertificates()`, o seu callback recebe um array com os certificados disponíveis na máquina do usuário.
Cada elemento do array retornado é um objeto do tipo
[CertificateModel](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.certificatemodel.html)
contendo informações sobre o certificado:
```javascript
pki.listCertificates().success(function (certs) {
for (var i = 0; i < certs.length; i++) {
var cert = certs[i];
console.log(cert.subjectName);
console.log(cert.email);
console.log(cert.validityEnd);
console.log(cert.pkiBrazil.cpf);
console.log(cert.pkiItaly.codiceFiscale);
}
});
```
Esse mesmo objeto é recebido como argumento para o callback `selectOptionFormatter` que pode ser passado na chamada à função `listCertificates()`:
```javascript
pki.listCertificates({
selectId: 'certificateSelect',
selectOptionFormatter: function (cert) {
var text = cert.subjectName + ' ('
+ 'emitido por ' + cert.issuerName
+ ' em ' + cert.validityEnd.toLocaleDateString()
+ ')';
if (new Date() > cert.validityEnd) {
text = '[EXPIRADO] ' + text;
}
return text;
}
});
```
Algumas das propriedades disponíveis são:
* `subjectName`: Campo *Common Name* (CN) do nome do titular
* `email`: Endereço de e-mail do titular
* `validityEnd`: Data de expiração do certificado (tipo `Date` padrão de JavaScript)
* `pkiBrazil`: Objeto com campos específicos da ICP-Brasil
* `pkiBrazil.cpf`: CPF do titular/responsável
* `pkiBrazil.cnpj`: CNPJ da empresa (ou `null`, caso não seja um certificado de pessoa jurídica)
* `pkiItaly`: Objeto com campos específicos de certificados italianos
* `pkiItaly.codiceFiscale`: *codice fiscale* do titular
> **Observação**
>
> As propriedades `pkiBrazil` e `pkiItaly` estão sempre preenchidas, mesmo que o certificado não seja um certificado brasileiro ou italiano,
> de modo que é seguro fazer algo como `if (cert.pkiBrazil.cpf) { ... }` sem risco de erros. Nesse caso, as sub-propriedade (`pkiBrazil.*` /
> `pkiItaly.*`) é que estarão com valor `null`.
Para obter uma lista completa das propriedades disponíveis em cada elemento do array, veja o guia da API para o tipo
[CertificateModel](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.certificatemodel.html).
# [Personalizando a listagem de certificados](https://docs.lacunasoftware.com/articles/web-pki/cert-select)
É possível personalizar o texto mostrado para cada certificado na listagem de certificados utilizando as
[Propriedades dos certificados](https://docs.lacunasoftware.com/articles/web-pki/cert-properties) retornadas pelo Web PKI:
```js
function onWebPkiReady() {
pki.listCertificates({
selectId: 'certificateSelect',
selectOptionFormatter: getCertText
});
}
function getCertText(cert) {
return cert.subjectName + ' (issued by ' + cert.issuerName + ')';
}
```
O mesmo se aplica se você estiver processando o array de certificados manualmente:
```js
function onWebPkiReady() {
pki.listCertificates().success(function (certs) {
var select = $('#certificateSelect');
for (var i = 0; i < certs.length; i++) {
var cert = certs[i];
select.append(
$(' ').val(cert.thumbprint).text(getCertText(cert))
);
}
});
}
function getCertText(cert) {
return cert.subjectName + ' (issued by ' + cert.issuerName + ')';
}
```
Por exemplo, para exibir a **data de validade** de cada certificado:
```js
function getCertText(cert) {
return cert.subjectName + ' (expires on ' + cert.validityEnd.toLocaleDateString() + ')';
}
```
Ou para exibir um prefixo `[EXPIRADO]` se o certificado estiver expirado:
```js
function getCertText(cert) {
var text = cert.subjectName;
if (new Date() > cert.validityEnd) {
text = '[EXPIRADO] ' + text;
}
return text;
}
```
> **Dica**
>
> Recomendamos utilizar pelo menos uma propriedade adicional além do nome do titular para ajudar usuários com
> mais de um certificado com o mesmo nome (em outras palavras, não use apenas `return cert.subjectName;`)
## Validação de certificados
Essa seção foi movida para um artigo separado: [Pré-validação de certificados](https://docs.lacunasoftware.com/articles/web-pki/cert-validation)
## Ordenação
Essa seção foi removida porque o Web PKI agora retorna automaticamente a lista de certificados ordenada pelo `subjectName`.
> **Observação**
>
> Caso na sua aplicação os certificados não estejam ordenados, atualize a biblioteca de JavaScript do Web PKI.
**Demonstrações e exemplos**
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- List the user's certificates with Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/ListCertJQueryController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ListCertJQueryController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/list-cert-jquery.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/list_cert_jquery.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/list_cert_jquery_controller.rb)
- Read a certificate's details with Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/ReadCertJQueryController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ReadCertJQueryController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/read-cert-jquery.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/read_cert_jquery.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/read_cert_jquery_controller.rb)
# [Pré-validação de certificados](https://docs.lacunasoftware.com/articles/web-pki/cert-validation)
É uma boa abordagem listar todos os certificados disponíveis -- sem filtrar por CPF ou data de validade -- e, uma vez que o usuário
escolha um certificado e clique no botão *assinar* ou *entrar*, validar o certificado escolhido.
> **Observação**
>
> Essa validação é meramente uma "pré-verificação" baseada nas informações do certificado, feita para evitar erros óbvios de escolha
> do certificado por parte do usuário. Essa **não é** a validação que de fato precisa ser feita para implementar um
> [login com certificado digital](https://docs.lacunasoftware.com/articles/pki-guide/cert-auth) ou uma [assinatura digital](https://docs.lacunasoftware.com/articles/pki-guide/signatures).
```js
var certificates = null;
function onWebPkiReady () {
pki.listCertificates({
selectId: 'certificateSelect'
// não passe o argumento "filter", para mostrar todos os certificados
}).success(function (certs) {
// NOTA: precisamos guardar o array retornado!
certificates = certs;
});
}
function getSelectedCert() {
var selectedCertThumb = $('#certificateSelect').val();
for (var i = 0; i < certificates.length; i++) {
var cert = certificates[i];
if (cert.thumbprint == selectedCertThumb) {
return cert;
}
}
return null;
}
function sign() {
// obtemos o certificado escolhido
var selectedCert = getSelectedCert();
// realizamos a pré-validação do certificado
if (new Date() > selectedCert.validityEnd) {
alert('O certificado escolhido expirou em ' + selectedCert.validityEnd.toLocaleDateString());
return;
}
// prossiga com o processo de assinatura ou login com o parâmetro "selectedCert.thumbprint"
}
```
> **Dica**
>
> Estamos utilizando a função `alert()` apenas para simplificar o exemplo, o ideal é utilizar um componente mais amigável para o usuário
> **Observação**
>
> Infelizmente o Web PKI ainda não suporta obter as propriedades de um certificado específico. Por isso, precisamos armazenar o array de
> certificados retornado pela função `listCertificates()` para consultá-lo mais tarde. Esse problema será corrigido numa versão futura
> do componente.
# [Customizando o comportamento quando o componente não estiver instalado](https://docs.lacunasoftware.com/articles/web-pki/customizing-not-installed)
Na seção [Primeiros Passos](https://docs.lacunasoftware.com/articles/web-pki/get-started), vimos que o método `init()` deve ser chamado antes de qualquer outra chamada:
```javascript
pki.init(onWebPkiReady);
```
Esse método verifica que as seguintes condições são válidas:
1. O usuário está utilizando um browser que é suportado ([clique aqui](https://docs.lacunasoftware.com/articles/web-pki/browser-support) para visualizar os browsers suportados);
1. Todos os componentes necessários estão instalados;
1. Todos os componentes estão atualizados.
Se tudo estiver OK, o callback passado como argumento é chamado. Entretanto, se alguma das condições acima não for válida, o usuário é redirecionado para a página
de instalação do componente de uma forma específica tal que, quando a instalação for concluída, ele será automaticamente redirecionado para a página original.
Entretanto, é possível customizar esse comportamento. Por exemplo, você pode querer primeiro exibir uma mensagem para o usuário explicando o problema e informando
que ele será redirecionado para a página de instalação antes de efetivamente redirecionar o usuário. Para isso, você deve usar a forma extensa de chamar o método
`init()` (consulte a [documentação do método](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#init) para mais detalhes) e passar um callback
no argumento `notInstalled`.
```javascript
pki.init({
ready: onWebPkiReady,
notInstalled: onWebPkiNotInstalled
});
function onWebPkiNotInstalled (status, message) {
alert(message + '\n\nVocê será redirecionado para a página de instalação.');
pki.redirectToInstallPage();
}
```
> **Observação**
>
> Embora o nome do argumento seja *notInstalled*, o callback passado é chamado caso qualquer uma das 3 condições testadas falhe (componente não instalado,
> desatualizado ou browser não suportado).
Tipicamente você irá em algum momento redirecionar o usuário para a página de instalação. Para isso, chame o método `redirectToInstallPage()` como mostrado acima.
Para mais informações sobre os argumentos passados para o callback `notInstalled`, consulte a
[documentação do método](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#init).
# [Especificando a URL do Rest PKI](https://docs.lacunasoftware.com/articles/web-pki/customizing-restpki-url)
Na seção [Primeiros Passos](https://docs.lacunasoftware.com/articles/web-pki/get-started), vimos que o método [`init()`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#init) deve ser chamado antes de qualquer outra chamada:
```javascript
pki.init(onWebPkiReady);
```
Caso seja utilizado um RestPKI não padrão, ou seja, diferente de https://pki.rest, é necessário configurar a licença do WebPKI e a URL do RestPKI que será utilizado na chamada do `init()`:
```javascript
var pki = new LacunaWebPKI('ASYAanNmaWRkbGUubmV0LHdlYnBraS5sYWN1bmFzb2Z0d2FyZS5jb20AAAABClKvO1J22vAD+YmfANiKQLbcLE1lNraPKCel6tRM+ZxR+h6M/crtJYRRVGGz7hrdbM0Y0mfTu15RMYGqQMi1QNZS6GrT4vNzIayv552Fl0EFWQA7jWlctUwfYoHRHVEnCNx9YGXDiA9+yDoGlVwgTR7fjzNeS3Fen1MVIyKBF464gN0JvdiCRJMI47JGVDkPmKjcrYIvJs6y5Lg25RW4ZnBKVruS+HR2s3k8ZrV4y4RCQE4UYMKbukF9vsF+JqAEifRlPq2xLcrNdxBveVDSXS/LRHAcrZrMM+Iw4A79jl0ngWPcy+CwinAhT+3dxVo5ZWMRQFpmTkylEMDvTjV9wQ==');
pki.init({
restPkiUrl: 'https://YourRestPKI.com/',
ready: onWebPkiReady,
});
```
> **Observação**
>
> Caso o WebPKI não esteja sendo utilizado em conjunto com o RestPKI, essa configuração é desnecessária.
Sem o `restPkiUrl`, o Web PKI acessa a instância padrão pela URL `https://restpki.lacunasoftware.com/`, um endereço
alternativo de https://pki.rest que, por ser mais convencional, tem uma chance menor de ser bloqueado pelos firewalls das
redes dos usuários. Se uma rede bloqueá-lo, o responsável por ela deve liberar o domínio `restpki.lacunasoftware.com` em
HTTPS. Veja [Erro "Could not get pending signature"](https://docs.lacunasoftware.com/articles/rest-pki/troubleshoot/could-not-get-pending-sig).
Para mais informações sobre o [`init()`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#init), consulte a
[documentação do método](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#init).
# [Callback de erro padrão](https://docs.lacunasoftware.com/articles/web-pki/default-error-callback)
Embora seja possível registrar callbacks de erro específicos em cada chamada assíncrona, se você vai sempre empregar a mesma lógica quando um erro ocorrer,
é mais prático registrar um **callback de erro padrão** ao chamar o método [`init()`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#init):
```javascript
pki.init({
ready: onWebPkiReady,
defaultFail: onWebPkiFail
});
function onWebPkiFail(ex) {
alert(ex.userMessage + ' (' + ex.code + ')');
console.log('Web PKI error originated at ' + ex.origin + ': (' + ex.code + ') ' + ex.error);
}
```
A partir de então, você pode registrar apenas o callback de sucesso em cada chamada assíncrona.
> **Observação**
>
> Quando um erro ocorre e não há callback de erro registrado para a operação, o callback de erro padrão é chamado. Se também não houver callback de erro padrão registrado,
> uma exceção é lançada.
## Códigos de erro
O campo `code` da exceção recebida pelo callback de erro indica qual erro ocorreu:
| Código | Significado |
| --- | --- |
| `undefined` | Erro indefinido. |
| `internal` | Erro interno. |
| `user_cancelled` | O usuário cancelou a operação. |
| `os_not_supported` | O sistema operacional do usuário não é suportado. |
| `addon_timeout` | O add-on do Internet Explorer não respondeu. |
| `addon_not_detected` | O add-on do Internet Explorer não foi detectado: não está instalado ou não está funcionando bem. |
| `addon_send_command_failure` | O add-on do Internet Explorer não conseguiu enviar o comando ao componente nativo. |
| `certificate_not_found` | O certificado selecionado não foi encontrado no repositório do usuário nem no dispositivo criptográfico. Se for usado um dispositivo criptográfico, verifique se os drivers ou a configuração PKCS #11 estão corretos. |
| `command_unknown` | O comando requisitado é desconhecido. |
| `command_not_supported` | O comando requisitado não é suportado no sistema operacional do usuário. |
| `command_parameter_not_set` | Falta na requisição um parâmetro obrigatório do comando. |
| `command_invalid_parameter` | Um parâmetro do comando não é válido. |
| `command_parameter_not_supported` | Um parâmetro do comando não é suportado nesta plataforma. |
| `native_connect_failure` | A extensão do navegador não conseguiu se conectar ao componente nativo. |
| `native_disconnected` | O componente nativo se desconectou da extensão do navegador. |
| `native_no_response` | A extensão do navegador não recebeu resposta do componente nativo. |
| `rest_pki_get_pending_signature` | Erro ao obter a assinatura pendente no método `signWithRestPki()`. Veja [Erro "Could not get pending signature"](https://docs.lacunasoftware.com/articles/rest-pki/troubleshoot/could-not-get-pending-sig). |
| `rest_pki_post_signature` | Erro ao enviar a assinatura no método `signWithRestPki()`. |
| `rest_pki_invalid_certificate` | O Rest PKI não aceitou o certificado selecionado. |
| `license_not_set` | Uma licença é necessária e não foi configurada. Veja [Licenciamento](https://docs.lacunasoftware.com/articles/web-pki/licensing). |
| `license_invalid` | A licença informada não é válida. |
| `license_restricted` | A licença informada não permite o comando requisitado. |
| `license_expired` | A licença informada expirou. |
| `license_domain_not_allowed` | A licença informada não permite o domínio em uso. |
| `validation_error` | Erro de validação. |
| `p11_error` | Erro na comunicação PKCS #11. |
| `p11_token_not_found` | O dispositivo selecionado não foi encontrado: está desconectado ou o módulo PKCS #11 não foi reconhecido. |
| `p11_not_supported` | O dispositivo não suporta a operação PKCS #11 requisitada. |
| `keyset_not_found` | A chave privada do certificado selecionado não foi encontrada. |
| `csp_keyset_not_defined` | Chave do certificado não encontrada: verifique se o smart card ou token USB do certificado selecionado está conectado ao computador, ou remova-o e insira-o novamente. |
| `mobile_send_message` | Na integração com dispositivo móvel, a biblioteca não conseguiu enviar o comando ao dispositivo. |
| `mobile_timeout` | Na integração com dispositivo móvel, o dispositivo não respondeu a tempo. |
A biblioteca também define estes códigos, ainda sem descrição: `algorithm_not_supported`, `signed_pdf_to_mark`, `json_error`,
`io_error`, `keychain_error`, `keychain_sign_error`, `decode_error`, `csp_invalid_algorithm`, `csp_invalid_provider_type`,
`mobile_not_authorized`, `command_decrypt_error`, `blocked_domain` e `invalid_operation`.
## Veja também
* [Documentação do parâmetro `userMessage`](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.exceptionmodel.html#usermessage) (disponível desde a [versão 1.4 da API](https://docs.lacunasoftware.com/articles/web-pki/api-reference/versions#v1-4))
* [Documentação do *callback* de erro FailCallback](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.failcallback.html) (disponível desde a [versão 1.3 da API](https://docs.lacunasoftware.com/articles/web-pki/api-reference/versions#v1-3))
* [Documentação da classe Promise](https://docs.lacunasoftware.com/content/typedocs/web-pki/interfaces/_lacuna_web_pki_d_.promise.html)
* [Documentação do enum ErrorCodes](https://docs.lacunasoftware.com/content/typedocs/web-pki/enums/_lacuna_web_pki_d_.lacunawebpki.errorcodes.html)
# [Filtros de certificados](https://docs.lacunasoftware.com/articles/web-pki/filters)
É possível filtrar os certificados exibidos na seleção de certificados da sua página passando um filtro para a função `listCertificates()`.
> **Aviso**
>
> Filtrar certificados pode gerar chamados de suporte indesejados. Os usuários podem esperar ver certos certificados que estão sendo
> filtrados e supor, equivocadamente, que eles não aparecem por causa de um bug e, por isso, procurar o suporte. Isso vale especialmente quando
> se aplica um filtro baseado na data de expiração, já que um certificado pode aparecer em um dia e "sumir" no dia seguinte. Por isso,
> **RECOMENDAMOS FORTEMENTE** que você **NÃO USE ESTA FUNCIONALIDADE**. Em vez disso, exiba todos os certificados e [valide o certificado escolhido](https://docs.lacunasoftware.com/articles/web-pki/cert-validation).
Se, por algum motivo, você ainda quiser usar esta funcionalidade apesar do aviso acima, continue lendo.
Para exibir apenas certificados dentro do prazo de validade:
```js
pki.listCertificates({
filter: pki.isWithinValidity
}).success(...);
```
Para exibir apenas certificados que tenham um CPF:
```js
pki.listCertificates({
filter: pki.hasPkiBrazilCpf
}).success(...);
```
> **Dica**
>
> Isso equivale, de forma aproximada, a "exibir apenas certificados ICP-Brasil", já que todo certificado ICP-Brasil tem um CPF (mesmo os certificados de empresa).
> **Observação**
>
> Observe que o fato de um certificado ter um CPF não significa, de fato, que ele seja confiável como um certificado válido emitido no âmbito da
> infraestrutura de chaves públicas ICP-Brasil, já que há uma série de validações que precisam ser feitas
> (veja [Login com certificado digital](https://docs.lacunasoftware.com/articles/pki-guide/cert-auth) para mais detalhes).
Para exibir apenas certificados que tenham um determinado CPF:
```js
pki.listCertificates({
filter: pki.pkiBrazilCpfEquals('11111111111')
}).success(...);
```
Você também pode combinar filtros, exigindo que os certificados atendam a **todos** os critérios informados:
```js
pki.listCertificates({
filter: pki.filters.all(pki.isWithinValidity, pki.hasPkiBrazilCpf)
}).success(...);
```
Ou a **qualquer um** dos critérios informados:
```js
pki.listCertificates({
filter: pki.filters.any(pki.hasPkiBrazilCpf, pki.hasPkiItalyCodiceFiscale)
}).success(...);
```
Você também pode escrever o seu próprio filtro:
```js
pki.listCertificates({
filter: function (cert) {
var now = new Date();
return (cert.validityStart <= now && now <= cert.validityEnd);
}
}).success(...);
```
Como dito acima, **recomendamos fortemente que você não use esta funcionalidade**, para evitar gerar chamados de suporte desnecessários.
Em vez disso, recomendamos exibir todos os certificados e [validar o certificado escolhido](https://docs.lacunasoftware.com/articles/web-pki/cert-select#validation).
# [Primeiros passos](https://docs.lacunasoftware.com/articles/web-pki/get-started)
## Pré-requisitos
Antes de começar, você precisará de um certificado digital válido registrado no repositório de certificados do Windows (o registro é feito automaticamente quando você utiliza um certificado digital no Windows). Para verificar se você tem um certificado digital registrado no Windows, siga os passos abaixo:
1. **Iniciar** > **Executar...** > **certmgr.msc**
1. Deve haver pelo menos um certificado na pasta Pessoal. Dê um duplo-clique no certificado.
1. Aparecerá uma janela com informações sobre o certificado. Deve haver a mensagem "Tem uma chave particular correspondente a este certificado."
Pronto, você já pode começar.
## Codificando as primeiras linhas
Primeiro, inclua na sua página o arquivo [lacuna-web-pki.js](https://cdn.lacunasoftware.com/libs/web-pki/lacuna-web-pki-2.18.0.min.js) mais recente:
```html
```
Ou instale através do [Pacote NPM web-pki](https://www.npmjs.com/package/web-pki):
```sh
$ npm install web-pki --save
```
> **Observação**
>
> Não é necessário incluir nenhum arquivo CSS pois o componente não interfere na interface da página.
Em seguida, instancie a classe `LacunaWebPKI`:
```javascript
var pki = new LacunaWebPKI();
```
Chame então o método `init()` passando um callback que será chamado quando o componente estiver pronto para ser usado:
```javascript
pki.init(onWebPkiReady);
function onWebPkiReady() {
// ...
}
```
Uma vez que o seu callback tenha sido chamado, você pode chamar as funções para operar com certificados digitais. Para obter os certificados disponíveis, utilize o método `listCertificates()`:
```javascript
pki.listCertificates().success(function (certs) {
for (var i = 0; i < certs.length; i++) {
var cert = certs[i];
var thumbprint = cert.thumbprint;
var subjectName = cert.subjectName;
var issuerName = cert.issuerName;
}
});
```
> **Observação**
>
> Se você não estiver familharizado com chamadas assíncronas, veja a seção [Chamadas assícronas](https://docs.lacunasoftware.com/articles/web-pki/async).
> **Dica**
>
> Para saber as propriedades disponíveis para cada certificado, veja o artigo [Propriedades dos certificados](https://docs.lacunasoftware.com/articles/web-pki/cert-properties)
Para ler o *encoding* de um certificado, chame o método `readCertificate()`:
```javascript
// Você precisará do thumbprint do certificado (conforme informado pelo método listCertificates)
var selectedCertThumbprint = '...';
pki.readCertificate({ thumbprint: selectedCertThumbprint }).success(function (certEncoding) {
// certEncoding contém o encoding do certificado (em Base64)
});
```
Para assinar bytes utilize o método signData(), que recebe 3 entradas:
* O *thumbprint* do certificado
* Os bytes a serem assinados (em Base64)
* O algoritmo de digest a ser utilizado durante a operação de assinatura
```javascript
pki.signData({
thumbprint: '...', // conforme informado pelo método listCertificates
data: '...', // bytes em Base64 (tipicamente dado pelo servidor)
digestAlgorithm: '...' // por exemplo 'SHA-256' (tipicamente dado pelo servidor)
}).success(function (signature) {
// signature contém o resultado da assinatura (em Base64)
});
```
Caso o seu SDK rodando no servidor lhe informe o hash dos bytes a serem assinados, ao invés dos bytes em si, chame o método `signHash()`:
```javascript
pki.signHash({
thumbprint: '...', // conforme informado pelo método listCertificates
hash: '...', // hash em Base64 (tipicamente dado pelo servidor)
digestAlgorithm: '...' // por exemplo 'SHA-256' (tipicamente dado pelo servidor)
}).success(function (signature) {
// signature contém o resultado da assinatura (em Base64)
});
```
## Exemplo online
[Embedded content](https://jsfiddle.net/LacunaSoftware/6zkwejb9/embedded/)
Para executar o exemplo, clique na aba **Result**. Se essa for a primeira vez que você utiliza o Web PKI, você será redirecionado para a página de instalação e voltará ao exemplo automaticamente após a instalação.
> **Observação**
>
> O exemplo acima só funcionará na sua aplicação web se a mesma estiver no **localhost**. Isso significa que você pode testar o Web PKI à vontade sem pagar nada, desde que a
> sua aplicação web esteja rodando no localhost (ou seja, em desenvolvimento). Para que o código funcione fora do localhost (em produção), você precisará de uma licença de uso.
> Para mais informações, acesse o artigo [Licenciamento](https://docs.lacunasoftware.com/articles/web-pki/licensing).
> **Dica**
>
> O exemplo acima prioriza a simplicidade do código. Veja os [exemplos completos](https://docs.lacunasoftware.com/articles/web-pki/full-samples) para códigos mais próximos da realidade.
## Próximos passos
Agora que vimos as funções básicas do Web PKI ...
* Veja os [browser suportados](https://docs.lacunasoftware.com/articles/web-pki/browser-support)
* Veja como funciona o [licenciamento](https://docs.lacunasoftware.com/articles/web-pki/licensing)
* Veja [como customizar o comportamento quando o componente não estiver instalado](https://docs.lacunasoftware.com/articles/web-pki/customizing-not-installed)
* Se a sua aplicação utiliza AngularJS ou Angular2+, leia a seção [Callbacks e Angular](https://docs.lacunasoftware.com/articles/web-pki/angularjs)
* Veja os [exemplos completos](https://docs.lacunasoftware.com/articles/web-pki/full-samples)
* Veja o [Guia da API](https://docs.lacunasoftware.com/articles/web-pki/api-reference)
**Demonstrações e exemplos**
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- List the user's certificates with Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/ListCertJQueryController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ListCertJQueryController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/list-cert-jquery.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/list_cert_jquery.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/list_cert_jquery_controller.rb)
- Read a certificate's details with Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/ReadCertJQueryController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ReadCertJQueryController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/read-cert-jquery.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/read_cert_jquery.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/read_cert_jquery_controller.rb)
- Sign a local PDF in the browser with Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureWebController.cs)
- Sign a local file with CAdES in the browser using Web PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesSignatureWebController.cs)
# [Substituição do ICPBravoAccess](https://docs.lacunasoftware.com/articles/web-pki/icpbravo-replacement)
A substituição da biblioteca ICPBravoAccess pelo plugin Web PKI pode ser feita
de uma maneira fácil e sem a necessidade de alterar seu código JavaScript existente.
Basta seguir os passos:
1. **Remova** a tag `
```
1. Adicione por último na tag `` do seu HTML:
```html
...
{/* Adicionar as seguintes linhas */}
...
```
> **Observação**
>
> **Licenciamento**
>
> Para utilizar o [Web PKI](http://docs.lacunasoftware.com/en-us/articles/web-pki/index.html) em
> ambiente de produção, é necessária a inclusão da licença através do parâmetro `window.lacunaWebPkiLicense`
> acima. Caso ainda não tenha uma licença, solicite o envio através do email **[suporte@lacunasoftware.com](mailto:suporte@lacunasoftware.com)**
> informando os *hostnames* ou *URLs* nos quais o plugin será executado.
Pronto, o Web PKI deve agora substituir completamente o ICPBravoAccess, adicionando suporte a outros navegadores
e sistemas como Mac OS X, Linux, Android e iOS.
## Veja também
* [Sistemas suportados](https://docs.lacunasoftware.com/articles/web-pki/browser-support)
* [Guia da API](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html)
# [Instalação e distribuição de componentes do WEB PKI via GPO](https://docs.lacunasoftware.com/articles/web-pki/installation-webpki-gpo)
Para distribuição automática dos plugins, extraia os arquivos do ZIP
([webpki-ext-2.16.0.zip](https://cdn.lacunasoftware.com/webpki/webpki-ext-2.16.0.zip)) para um **diretório compartilhado** entre os usuários.
Como exemplo, vamos usar o **diretório compartilhado**: `C:\shared\webpki-ext`
## Distribuição automática do plugin no Chrome
Criar uma chave com caminho: `HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Google\Chrome\Extensions\dcngeagmmhegagicpcmpinaoklddcgon`
Adicione as *Strings Value*:
```text
Name: update_url
Data: https://clients2.google.com/service/update2/crx
```
```text
Name: path
Data: C:\shared\webpki-ext\webpki-chrome-ext.crx
```
```text
Name: version
Data: 2.15.0
```
Como na imagem abaixo:

Criar uma chave com caminho: `HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\ExtensionInstallForcelist`
E adicionar uma *String Value*
```text
Name: 1 (ou o próximo inteiro sequencial se já houver)
Data: dcngeagmmhegagicpcmpinaoklddcgon;https://clients2.google.com/service/update2/crx
```
Como na imagem abaixo:

## Distribuição automática do plugin no Firefox
Criar uma chave com caminho: `HKEY_LOCAL_MACHINE\Software\Policies\Mozilla\Firefox\Extensions\Install`
E adicionar uma String Value:
```text
Name: 1 (ou o próximo inteiro sequencial se já houver)
Data: C:\shared\webpki-ext\webpki-firefox-ext.xpi
```
Como na imagem abaixo:

## Distribuição automática da aplicação nativa
Baixar o instalador MSI do link e distribuir via GPO:
https://get.webpkiplugin.com/downloads/latest/setup-win-pt
## Veja também
* Veja os [browser suportados](https://docs.lacunasoftware.com/articles/web-pki/browser-support)
* Veja como funciona o [licenciamento](https://docs.lacunasoftware.com/articles/web-pki/licensing)
* Veja [como customizar o comportamento quando o componente não estiver instalado](https://docs.lacunasoftware.com/articles/web-pki/customizing-not-installed)
# [Licenciamento](https://docs.lacunasoftware.com/articles/web-pki/licensing)
Para utilizar o Web PKI em aplicações web rodando fora do localhost, é preciso configurar a licença de uso do componente. Para
configurar a licença, basta escolher um dos dois formatos recebidos e passar no construtor da classe `LacunaWebPKI`.
Caso prefira o formato binário:
```javascript
var webPkiLicense = 'ASYAanNma...Q==';
var pki = new LacunaWebPKI(webPkiLicense);
```
Ou, se preferir o formato em JSON:
```javascript
var webPkiLicense = {
"format": 1,
"allowedDomains": [
"www.patorum.com"
],
...
};
var pki = new LacunaWebPKI(webPkiLicense);
```
Se você não passou a licença no construtor, pode passá-la no argumento `license` do método
[`init()`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#init), em qualquer um dos formatos:
```javascript
var pki = new LacunaWebPKI();
pki.init({
license: webPkiLicense,
ready: onWebPkiReady
});
```
> **Observação**
>
> A licença do Web PKI não é necessária para testar o Web PKI em uma aplicação web rodando no localhost (qualquer porta)
> ou ainda quando sendo utilizado em conjunto com o Rest PKI em nuvem (nesse caso, inclusive em produção).
Para mais informações sobre a diferença entre os formatos de licença, veja a seção abaixo.
## Formatos de licença
Você receberá a licença em dois formatos, como abaixo:
```javascript
// Binary license
var webPkiLicense = 'ASYAanNma...Q==';
// JSON license
var webPkiLicense = {
"format": 1,
"allowedDomains": [
"www.patorum.com"
],
"homologDomains": [
"staging.patorum.com",
"ip4:10.0.0.0/8",
"ip4:127.0.0.0/8",
"ip4:172.16.0.0/12",
"ip4:192.168.0.0/16"
],
"productLevel": "Standard",
"expiration": null,
"signature": "LzdT1cgp...w=="
};
```
Os dois formatos são equivalentes. A diferença é que o formato binário esconde os detalhes da sua licença, enquanto que no formato json eles são legíveis.
Se você não se importar que usuários que por ventura inspecionem o código fonte da página vejam os detalhes da sua licença, o formato json é preferível pois
facilita o diagnóstico de problemas como uma licença expirada. Entretanto, se você preferir esconder essas informações, utilize o formato binário. Note que
os detalhes não ficam cifrados no formato binário, apenas codificados em Base64.
> **Aviso**
>
> O valor do campo `signature` da licença em formato JSON, embora se assemelhe à versão binária, não funciona como licença sozinho. Utilize ou a versão
> binária ou a versão JSON inteira. Qualquer tentativa de usar apenas algumas partes da licença resultará em rejeição da licença pelo componente.
## Uso em IPs internos
As licenças de uso do Web PKI são emitidas permitindo uso em caráter de homologação em qualquer IP privado:
```javascript
...
"homologDomains": [
...,
"ip4:10.0.0.0/8",
"ip4:127.0.0.0/8",
"ip4:172.16.0.0/12",
"ip4:192.168.0.0/16"
],
...
```
Isso significa que todos os IPs das faixas privadas de IPs podem ser utilizados:
* 10.x.x.x
* 127.x.x.x
* 172.16.0.0 a 172.31.255.255
* 192.168.x.x
Por exemplo, um site de hologação hospedado em `http://192.168.5.12:8000/signature/` estaria contemplado.
# [Logs - Web PKI](https://docs.lacunasoftware.com/articles/web-pki/logs)
O [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) pode registrar logs tanto do componente do navegador quanto da aplicação nativa.
## Habilitar logs no console do navegador
Para habilitar o registro de logs detalhados no console da página do navegador e na aplicação nativa, clique no ícone da extensão e depois em
`Configurações > Avançadas > Logs detalhados`

## Logs da aplicação nativa
### Windows
No Windows, os logs são registrados no arquivo:
```text
%LOCALAPPDATA%\Lacuna Software\WebPKIForChrome.log
```
Nota: `%LOCALAPPDATA%` é um atalho para o diretório de dados de aplicação do usuário atual.
### Mac
No Mac, os logs são registrados no arquivo:
```text
~/.lacuna-webpki/WebPkiCore.log
```
Nota: `~` é um atalho no terminal para o diretório `home` do usuário atual.
### Linux
No Linux, os logs são registrados no arquivo:
```text
~/.lacuna-webpki/WebPkiCore.log
```
Nota: `~` é um atalho no terminal para o diretório `home` do usuário atual.
# [Atributos de Segurança - Web PKI](https://docs.lacunasoftware.com/articles/web-pki/security)

* A instalação dos componentes do plugin WebPKI é realizada unicamente e de forma centralizada no (https://get.webpkiplugin.com) servido com HTTPS estrito e com avaliação "A" na SSL Labs.
* Os componentes "Web Extension" são submetidos para as lojas oficiais de cada navegador suportado, e em seguida revisados e assinados digitalmente pelas lojas abaixo:
* Chrome webstore;
* Firefox ADD-NOS;
* Edge Add-nos;
* Safari App Store (em breve).
* Os componentes “Web Extension” possui seu código-fonte principal em JavaScript na forma original, não-ofuscado e não-minificado, de forma que todas as operações podem ser avaliadas ou auditadas por terceiros.
* O desenvolvimento do ecossistema do plugin WebPKI é realizado seguindo as melhores práticas de programação estando em conformidade com o que é exigido das plataformas oficiais da Microsoft e Apple.
* Os componentes "Native App" que são instalados na máquina do usuário, são baixados através do domínio centralizado e são assinados digitalmente com CodeSign EV e com carimbo de tempo DigiCert com validade de 10 anos. De modo que a instalação é realizada de forma confiável e autenticada pelos sistemas operacionais sem nenhum alerta de segurança.
* Os componentes "Native App" não necessitam de privilégios de administrador para sua execução.
* Os componentes "Native App" dão preferência para comunicação com os certificados e chaves do usuário via API/Repositório padrão do sistema operacional, de forma que PINs ou senhas de autenticação não são trafegadas pelo "Native App", são gerenciadas diretamente entre os "Crypto Devices" e o sistema operacional.
# [Atualizações - Web PKI](https://docs.lacunasoftware.com/articles/web-pki/update)
O [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) é atualizado automaticamente nos computadores dos usuários. Entretanto, você pode (e deve)
atualizar a biblioteca de Javascript (`lacuna-web-pki-{version}.js`) na sua aplicação web regularmente.
Atualizações à biblioteca de Javascript:
* Corrigem problemas que não podem ser resolvidos apenas através de atualizações ao componente instalado nos computadores dos usuários
* Permitem que a sua aplicação utilize novas funcionalidades do Web PKI
A última versão da biblioteca de Javascript é a **2.18.0**. Você pode obtê-la no link abaixo:
https://cdn.lacunasoftware.com/libs/web-pki/lacuna-web-pki-2.18.0.min.js
Ou incluir na sua página como:
```html
```
Ou instalar através do [Pacote NPM web-pki](https://www.npmjs.com/package/web-pki):
```sh
$ npm install web-pki --save
```
> **Dica**
>
> Todas as atualizações são 100% retrocompatíveis, de modo que você pode atualizar a biblioteca sem
> perigo de causar problemas à sua aplicação web existente
# [Referência rápida do Rest PKI (legado)](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference)
O essencial do Rest PKI numa só página, com links para os artigos completos.
> **Aviso: Produto legado**
>
> O Rest PKI, o serviço em nuvem em [pki.rest](https://pki.rest/), é um produto legado: continua recebendo correções de bugs e de segurança, mas, para uma nova integração em nuvem, use o [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/quick-reference). O Rest PKI Core também atende à maior parte das APIs do Rest PKI clássico (assinaturas PAdES, CAdES e XML, autenticação com certificado e marcas em PDF), o que facilita a migração de uma aplicação.
TODO(docs): não há guia para migrar uma aplicação do Rest PKI para o Rest PKI Core. O Rest PKI continua recebendo versões (1.33.9 em 2026-09-08).
## Para que serve
O Rest PKI é um serviço em nuvem para operações de certificação digital, como assinaturas digitais e autenticação com certificado, usado a partir de praticamente qualquer linguagem de programação por meio de bibliotecas cliente; também é possível hospedar uma instância própria em Windows Server ou Azure App Service.
Numa aplicação web, ele trabalha com o Web PKI: o seu backend inicia a operação e recebe um token de uso único, o `signWithRestPki()` do Web PKI assina no navegador e o seu backend conclui a operação com esse token.
| Prefira outro produto | Quando ele atende melhor |
| --- | --- |
| [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/quick-reference) | Você vai começar uma nova integração em nuvem: assinaturas, validação, carimbos de tempo e verificação biométrica por uma API REST, em core.pki.rest ou numa instância própria, com páginas de assinatura hospedadas por ele. |
| [PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference) | Você assina nos seus próprios servidores Windows ou Linux, a partir de praticamente qualquer linguagem. Para Linux, o artigo do Rest PKI on-premises recomenda o PKI Express. |
| [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/quick-reference) | O seu backend é .NET: o guia de certificação digital recomenda o PKI SDK para assinaturas no servidor em .NET, e ele pode fazer a parte do servidor das assinaturas web com o Web PKI. |
| [Web PKI](https://docs.lacunasoftware.com/articles/web-pki/quick-reference) | Você precisa só da parte do navegador: listar os certificados do usuário, lê-los e assinar com eles em JavaScript. O Rest PKI o usa nessa etapa. |
## Instalação
| Linguagem | Pacote | Major atual | Registry |
| --- | --- | --- | --- |
| .NET (C#, VB.NET, C++.NET) | `Lacuna.RestPki.Client` | 5 (mais recente: 5.0.0); 1.24.1 para .NET Framework 3.5 a 4.6 | [NuGet](https://www.nuget.org/packages/Lacuna.RestPki.Client/) |
| Java 7+ | `com.lacunasoftware.restpki:restpki-client` | 1 (mais recente: 1.15.11, para Java 8+; em Java 7, 1.15.7) | [Maven Central](https://search.maven.org/artifact/com.lacunasoftware.restpki/restpki-client) |
| Java 6 | `com.lacunasoftware.restpki:restpki-client-java6` | 1 (mais recente: 1.9.0) | [Maven Central](https://search.maven.org/artifact/com.lacunasoftware.restpki/restpki-client-java6) |
| PHP 5.5+ | `lacuna/restpki-client` | 2 (mais recente: 2.9.10) | [Packagist](https://packagist.org/packages/lacuna/restpki-client) |
| PHP 5.3 e 5.4 | `lacuna/restpki-client-legacy` | 1 (mais recente: 1.1.0) | [Packagist](https://packagist.org/packages/lacuna/restpki-client-legacy) |
| Node.js | `restpki-client` | 2 (mais recente: 2.3.8) | [npm](https://www.npmjs.com/package/restpki-client) |
| Python | `restpki-client` | 1 (mais recente: 1.3.1) | [PyPI](https://pypi.org/project/restpki-client/) |
| Ruby | `rest_pki` | 1 (mais recente: 1.1.0) | [RubyGems](https://rubygems.org/gems/rest_pki) |
- O .NET tem duas linhas de versões: a 1.24.1, a última 1.x, atende ao .NET Framework 3.5 a 4.6 e ao .NET Standard 1.3 e 2.0; a 2.2.0 e as seguintes atendem só ao .NET Standard 2.0, são publicadas com o Rest PKI Core e têm os mesmos números de versão do cliente dele, o `Lacuna.RestPkiCore.Client`. As duas linhas funcionam com o pki.rest; escolha pelo framework de destino. Veja [.NET](https://docs.lacunasoftware.com/articles/rest-pki/dotnet#versões-e-plataformas-suportadas).
- Java: as versões 1.15.9 e seguintes do `restpki-client` são compiladas para Java 8; em Java 7, use a 1.15.7. Veja [Java](https://docs.lacunasoftware.com/articles/rest-pki/java).
- O PHP 5.2 não tem pacote: use o arquivo `RestPkiLegacy52.php` com o pacote do Composer `educoder/pest`. Veja [PHP](https://docs.lacunasoftware.com/articles/rest-pki/php).
- As APIs REST não são feitas para chamadas diretas: use uma biblioteca cliente. Referências da API: [.NET](https://docs.lacunasoftware.com/api/Lacuna.RestPki.Client), [Java](https://docs.lacunasoftware.com/en-us/content/javadocs/restpki-client/) e a [cópia do OpenAPI](https://docs.lacunasoftware.com/openapi/rest-pki.json).
## Configuração e autenticação
- **Token de acesso**: crie uma conta em [pki.rest](https://pki.rest/), autentique-se e, no **Painel de Controle**, clique em **Gerar novo token de acesso**.
- **Cliente**: um `RestPkiClient` recebe o endpoint e o token de acesso, e todo starter, finisher e explorer recebe o cliente. Nas chamadas HTTP diretas, envie o token no header `Authorization: Bearer `.
- **Endpoint**: `https://pki.rest/` na nuvem; por padrão, o Web PKI chama o endereço alternativo `https://restpki.lacunasoftware.com/`, que tem menos chance de ser bloqueado por firewalls. On-premises, o endpoint é o endereço da sua instância, terminado em `/`, e a página o passa ao `init()` do Web PKI como `restPkiUrl`.
- **Contexto de segurança**: cada operação indica um contexto de segurança, que define as ACs raiz confiáveis; em produção, os exemplos usam `StandardSecurityContexts.PkiBrazil` (ICP-Brasil).
- **Licenças**: o serviço em nuvem não usa arquivo de licença, e o Web PKI não precisa de licença com ele, inclusive em produção. Uma instância on-premises exige uma licença do PKI SDK e, nesse caso, o Web PKI precisa da própria licença. Veja [Licenciamento](https://docs.lacunasoftware.com/articles/welcome/licensing#rest-pki).
- **Datas nas assinaturas de PDF**: cultura, fuso horário e formato de data vêm de **Região e Data** na sua conta. Veja [Configuração de data e hora](https://docs.lacunasoftware.com/articles/rest-pki/configure-date).
```cs
var accessToken = ConfigurationManager.AppSettings["RestPkiAccessToken"];
var endpoint = ConfigurationManager.AppSettings["RestPkiEndpoint"];
if (string.IsNullOrEmpty(endpoint)) {
endpoint = "https://pki.rest/";
}
return new RestPkiClient(endpoint, accessToken);
```
## Operações principais
O código é C#, do projeto de exemplos em ASP.NET MVC; as demais linguagens seguem o mesmo fluxo, veja [Disponibilidade de exemplos](https://docs.lacunasoftware.com/articles/rest-pki/samples-availability).
### Assinar um PDF
Assinatura PAdES com o certificado do usuário: `PadesSignatureStarter.StartWithWebPkiAsync()` devolve o token, a página chama o `signWithRestPki({ token, thumbprint })` do Web PKI e `PadesSignatureFinisher2.FinishAsync()` devolve o PDF assinado e o certificado do signatário. O token não é o token de acesso e só vale uma vez; por isso, o exemplo envia headers que desativam o cache. Para a representação visual, veja [Tags PAdES](https://docs.lacunasoftware.com/articles/rest-pki/pades-tags). Veja [Assinatura PAdES](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#pades).
```cs
// GET action
var signatureStarter = new PadesSignatureStarter(Util.GetRestPkiClient()) {
SignaturePolicyId = StandardPadesSignaturePolicies.Basic,
SecurityContextId = Util.GetSecurityContextId()
};
signatureStarter.SetPdfToSign(userfilePath);
var token = await signatureStarter.StartWithWebPkiAsync();
// POST action, after signWithRestPki() in the browser
var signatureFinisher = new PadesSignatureFinisher2(Util.GetRestPkiClient()) {
Token = model.Token
};
var result = await signatureFinisher.FinishAsync();
var signerCert = result.Certificate;
```
### Assinar qualquer arquivo com CAdES
Assinatura CMS/CAdES de qualquer arquivo, gravada como `.p7s`: `CadesSignatureStarter` e depois `CadesSignatureFinisher2`, como no fluxo do PDF. Para fazer uma assinatura conjunta, chame `SetCmsToCoSign()` com o CMS existente em vez de `SetFileToSign()`. Veja [Assinatura CAdES](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#cades).
```cs
var signatureStarter = new CadesSignatureStarter(Util.GetRestPkiClient()) {
SignaturePolicyId = StandardCadesSignaturePolicies.PkiBrazil.AdrBasica,
SecurityContextId = Util.GetSecurityContextId(),
EncapsulateContent = true
};
signatureStarter.SetFileToSign(userfilePath);
var token = await signatureStarter.StartWithWebPkiAsync();
```
### Assinar um documento XML
O `FullXmlSignatureStarter` assina o documento inteiro; o `XmlElementSignatureStarter`, com `SetToSignElementId()`, assina um elemento, como no exemplo de NF-e. Os dois concluem com o `XmlSignatureFinisher`. Veja [Assinatura XML](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#xml-full).
```cs
var signatureStarter = new FullXmlSignatureStarter(Util.GetRestPkiClient());
signatureStarter.SetXml(StorageMock.GetSampleXmlDocumentPath());
signatureStarter.SetSignaturePolicy(StandardXmlSignaturePolicies.XadesBes);
signatureStarter.SetSecurityContext(Util.GetSecurityContextId());
var token = await signatureStarter.StartWithWebPkiAsync();
```
### Validar uma assinatura
Abra um arquivo assinado e valide as assinaturas dele: `PadesSignatureExplorer` com `Validate = true` e depois `OpenAsync()`. Para CAdES, use o `CadesSignatureExplorer`; para XML, o `XmlSignatureExplorer`. Veja [Abrir uma assinatura PAdES](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#open-pades).
```cs
var sigExplorer = new PadesSignatureExplorer(Util.GetRestPkiClient()) {
Validate = true,
DefaultSignaturePolicyId = StandardPadesSignaturePolicies.Basic,
SecurityContextId = Util.GetSecurityContextId()
};
sigExplorer.SetSignatureFile(userfilePath);
var signature = await sigExplorer.OpenAsync();
```
### Gerar uma versão para impressão
Um PDF marcado com os signatários e um código de verificação: faça upload do arquivo com `UploadFile()`, valide-o com o `PadesSignatureExplorer` como acima e adicione as marcas com o `PdfMarker`, montadas com o `PdfHelper` da API fluente; o `AlphaCode` gera o código. Veja [Versão para impressão](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#print).
```cs
var client = Util.GetRestPkiClient();
var blob = client.UploadFile(pdfContent);
var pdfMarker = new PdfMarker(client);
pdfMarker.SetFile(blob);
pdfMarker.Marks.Add(manifestMark);
var result = pdfMarker.Apply();
```
### Autenticar com certificado
Login com certificado: `GetAuthentication()` e depois `StartWithWebPkiAsync()` com o contexto de segurança; depois que o Web PKI assina o token, `CompleteWithWebPkiAsync()` devolve os resultados da validação. Veja [Autenticação](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#auth).
```cs
// GET action
var auth = Util.GetRestPkiClient().GetAuthentication();
var token = await auth.StartWithWebPkiAsync(Util.GetSecurityContextId());
// POST action, after signWithRestPki() in the browser
var auth = Util.GetRestPkiClient().GetAuthentication();
var validationResults = await auth.CompleteWithWebPkiAsync(model.Token);
if (!validationResults.IsValid) {
return View("Failed", validationResults);
}
```
### Solicitar um carimbo de tempo
Carimbos de tempo ICP-Brasil, com o token de acesso como `Bearer`: por TSP (RFC 3161) em `POST https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310` ou por REST em `POST https://pki.rest/Api/TimestampPlans/a402df41-8559-47b2-a05c-be555bf66310/Issue`, que devolve `encodedValue` (o carimbo de tempo, em Base64) e `info`. Algoritmos: SHA-1 e SHA-256. Veja [Solicitando carimbos de tempo](https://docs.lacunasoftware.com/articles/rest-pki/requesting-timestamps).
```json
{
"algorithm": "SHA256",
"value": "L80RIvFw2jwpiObzFQN+tuEQQ4E9qnGeYYYd+4DrM8M="
}
```
## Erros
O Rest PKI responde com um código de status HTTP: 401 quando o token de acesso não é válido, 422 com um código de retorno quando uma operação falha e 500 para um erro inesperado. Veja [Códigos de retorno](https://docs.lacunasoftware.com/articles/rest-pki/return-codes).
O cliente .NET lança `ValidationException` para o código de erro `ValidationError`, cujos `ValidationResults` dizem o que falhou, e `RestPkiException` para os demais códigos, com o código em `ErrorCode`. Para HTTP 401 e outros códigos de status de erro, ele lança `RestErrorException`, com o status em `StatusCode`, e, quando nenhuma resposta chega (falha de rede ou timeout), `RestUnreachableException`. Todas derivam de `RestException`. Veja [Erros](https://docs.lacunasoftware.com/articles/rest-pki/dotnet#erros) no artigo de .NET. O cliente Java também tem `RestErrorException` e `RestUnreachableException`.
| Erro | O que significa |
| --- | --- |
| HTTP 401 | O token de acesso não é válido. |
| `ValidationError` | O certificado do signatário não estava válido na assinatura ou na autenticação; os erros de validação dizem o motivo, como `CertificateChainRootNotTrusted`. |
| `SecurityContextRequired` | Nenhum contexto de segurança foi informado. |
| "Could not get pending signature" (no navegador) | O Web PKI não conseguiu acessar o Rest PKI. Verifique se o computador do usuário abre `/Api/System/Info` na instância e, on-premises, o `restPkiUrl`. Veja [o artigo](https://docs.lacunasoftware.com/articles/rest-pki/troubleshoot/could-not-get-pending-sig). |
## Certificados de teste e credenciais
- [Certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#configurando-o-rest-pki): arquivos PKCS #12 com senha `1234`, da ICP de testes da Lacuna, que nunca deve ser confiada em produção.
- Para aceitá-los, use o contexto de segurança da ICP de testes da Lacuna, `803517ad-3bbc-4169-b085-60053a8f6dbf`. Os exemplos em .NET usam `StandardSecurityContexts.LacunaTest` só em builds de debug e avisam que uma instalação on-premises pode não tê-lo; os demais exemplos o usam quando `trustLacunaTestRoot` (`TRUST_LACUNA_TEST_ROOT` em Python) está ligado na configuração.
- **Token de acesso**: os projetos do PkiSuiteSamples vêm com um token de acesso de avaliação para o pki.rest, com a data de expiração num comentário ao lado (no exemplo em ASP.NET MVC, `RestPkiAccessToken` no `Web.config`). Para a sua aplicação, gere um token como em [Configuração e autenticação](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference#configuração-e-autenticação).
TODO(docs): nenhum ambiente de testes separado está documentado para o pki.rest.
## Experimente
Demonstrações e os exemplos do Rest PKI em cada linguagem:
**Demonstrações e exemplos**
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Sign a PDF with Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_signature_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_signature_rest_controller.rb)
- Sign a PDF with Rest PKI without browser access to Rest PKI: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-wo-communication-restpki.js)
- Sign multiple PDFs with Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/BatchPadesSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchPadesSignatureRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/batch-pades-signature-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/batch-pades-signature-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/batch_pades_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/batch_pades_rest_controller.rb)
- Sign a PDF with a server key using Rest PKI: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-server-key-rest/index.php)
- Sign a file with CAdES using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/CadesSignatureRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/cades-signature-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/cades_signature_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/cades_signature_rest_controller.rb)
- Sign multiple files with CAdES using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/BatchCadesSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchCadesSignatureRestControler.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/batch-cades-signature-restpki.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/batch_cades_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/batch_cades_rest_controller.rb)
- Sign an XML document using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/XmlSignatureRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-signature-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-signature-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/xml_signature_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/xml_signature_rest_controller.rb)
- Sign one element of an XML document using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlContractSignatureRestController.cs)
- Sign a Brazilian NF-e using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlNFeSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/XmlNFeSignatureRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-nfe-signature-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-nfe-signature-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/xml_nfe_signature_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/xml_nfe_signature_rest_controller.rb)
- Sign a COD XML using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlCodSignatureRestController.cs) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-cod-signature-rest/index.php)
- Sign a Brazilian digital diploma XML using Rest PKI: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/XmlDiplomaSignatureRestController.java)
- Validate a signed PDF using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenPadesSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenPadesRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/open-pades-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-pades-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/open_pades_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/open_pades_rest_controller.rb)
- Validate a CMS/CAdES signature using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenCadesSignatureRestController.cs) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/open-cades-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-cades-rest/index.php)
- Validate a signed XML using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenXmlSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenXmlRestController.java) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-xml-rest/index.php)
- Make a printer-friendly version of a signed PDF using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyPadesRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PrinterFriendlyPadesRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/printer-version-pades-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/printer-friendly-pades-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/printer_version_pades_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/printer_version_pades_rest_controller.rb)
- Make a printer-friendly version of a CMS/CAdES signature using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyCadesRestController.cs)
- Authenticate with a digital certificate using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/AuthenticationRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/authentication-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/authentication-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/authentication_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/authentication_rest_controller.rb)
- Sign a medical prescription (Receita Simples) using Rest PKI: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ReceitaSimplesRestController.java) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/receita-simples-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/django/sample/receita_simples_rest/views.py)
# [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki)
> **Aviso: Produto legado**
>
> O Rest PKI é um produto legado: continua recebendo correções de bugs e de segurança, mas novas integrações em nuvem
> devem usar o [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core), que também atende à maioria das APIs do Rest PKI. Veja a
> [referência rápida do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference).
O [Rest PKI](https://pki.rest/) é um serviço em nuvem para realizar operações de certificação digital como
assinaturas digitais e autenticação por certificado digital que pode ser utilizado a partir de virtualmente qualquer
linguagem de programação.
> **Dica**
>
> O Rest PKI faz parte da nossa linha de soluções para certificação digital. Recomendamos que você leia o artigo
> [Certificação Digital](https://docs.lacunasoftware.com/articles/pki-guide) para ter certeza de que esse é o produto mais indicado para o seu caso.
Embora o Rest PKI ofereça diversas APIs REST que podem ser facilmente consumidas, elas normalmente não são utilizadas
diretamente. Ao invés disso, oferecemos bibliotecas para consumir os serviços do Rest PKI (*client libs*) em diversas
linguagens de programação e diversos projetos de exemplos que demonstram o uso dessas bibliotecas, de modo que os
programadores não precisem se preocupar com os detalhes envolvidos no consumo de APIs e possam codificar diretamente
em sua linguagem preferida.
Escolha uma das linguagens de programação abaixo para começar:
* [PHP](https://docs.lacunasoftware.com/articles/rest-pki/php)
* [Java](https://docs.lacunasoftware.com/articles/rest-pki/java)
* [.NET](https://docs.lacunasoftware.com/articles/rest-pki/dotnet) (C#, VB.NET, C++.NET)
* [Python](https://docs.lacunasoftware.com/articles/rest-pki/python)
* [Node.js](https://docs.lacunasoftware.com/articles/rest-pki/nodejs)
* [Ruby](https://docs.lacunasoftware.com/articles/rest-pki/ruby)
> **Observação**
>
> Não encontrou a sua linguagem de programação favorita? [Fale com a gente](https://www.lacunasoftware.com/pt/home/purchase).
## *On premises*
Se por algum motivo você não possa ou não queira utilizar um serviço em nuvem, é possível hospedar sua própria
instância do Rest PKI na sua própria infraestrutura ("on premises"). Veja o artigo
[Rest PKI *on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) para mais informações.
**Demonstrações e exemplos**
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Sign a PDF with Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_signature_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_signature_rest_controller.rb)
- Sign a file with CAdES using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/CadesSignatureRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/cades-signature-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/cades_signature_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/cades_signature_rest_controller.rb)
# [Usando o Rest PKI em PHP](https://docs.lacunasoftware.com/articles/rest-pki/php)
Rest PKI pode ser usado em aplicação PHP usando uma enorme variedade de versões PHP. Para começar, escolha um dos projetos de amostras disponíveis:
* [PHP 5.5+](https://docs.lacunasoftware.com/articles/rest-pki/php/current) (incluindo PHP 7.x)
* [PHP 5.3/5.4](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy)
* [PHP 5.2](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy52)
### Biblioteca de clientes
O uso do Rest PKI em aplicações PHP dependem da versão PHP. Veja esta seção abaixo correspondente para seu caso.
### Para PHP 5.5+
O uso do Rest PKI no PHP 5.5+ (incluindo 7.x), você deve usar o pacote do Composer [lacuna/restpki-client](https://packagist.org/packages/lacuna/restpki-client).
Para adicionar o seu projeto, coloque isso no seu arquivo `composer.json`:
```json
{
"require": {
"lacuna/restpki-client": "^2.9.10"
}
}
```
Após isso, faça um `composer install` para download do pacote e e suas dependências (se você não tiver o Composer instalado, pegue [aqui](https://getcomposer.org/)).
O pacote é de código aberto, hospedado no [GitHub](https://github.com/LacunaSoftware/RestPkiPhpClient). Sinta-se à vontade para fazer o *fork* do repositório se precisar de alguma personalização.
### Para PHP 5.3 e 5.4
Para usar Rest PKI no PHP 5.3 e 5.4, você deve usar o pacote do Composer [lacuna/restpki-client-legacy](https://packagist.org/packages/lacuna/restpki-client-legacy).
Para adicionar o seu projeto, coloque isto em seu arquivo `composer.json`:
```json
{
"require": {
"lacuna/restpki-client-legacy": "^1.1.0"
}
}
```
Após isso, faça um `composer install` para download do pacote e e suas dependências (se você não tiver o Composer instalado, pegue [aqui](https://getcomposer.org/)).
O pacote é de código aberto, hospedado no [GitHub](https://github.com/LacunaSoftware/RestPkiPhpClientLegacy). Sinta-se à vontade para fazer o *fork* do repositório se precisar de alguma personalização.
### Para PHP 5.2
Para usar o Rest PKI no PHP 5.2, use o arquivo [RestPkiLegacy52.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/RestPkiLegacy52.php). Se solicitado o pacote [educoder/pest](https://packagist.org/packages/educoder/pest), que
precisa ser adicionado ao seu `composer.json`:
```json
{
"require": {
"educoder/pest": "1.0.0"
}
}
```
Após isso, faça um `composer install` para download do pacote e e suas dependências (se você não tiver o Composer instalado, pegue [aqui](https://getcomposer.org/)).
# [Como converter do PKI Express para o REST PKI em PHP](https://docs.lacunasoftware.com/articles/rest-pki/php/convert-from-express)
Este artigo explica as alterações necessárias para converter uma aplicação PHP utilizando [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) para uma aplicação PHP que utilize [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki).
## Equivalência de políticas
Ao migrar sua aplicação PHP que utiliza o PKI Express para utilizar o REST PKI, é importante se atentar aos nomes das políticas usadas na aplicação.
Na tabela a seguir, é possível encontrar as equivalências entre os nomes das políticas em ambas bibliotecas. Na primeira coluna temos os nomes das políticas na biblioteca PKI Express, e na segunda coluna temos os nomes das mesmas políticas só que na biblioteca REST PKI.
| PKI Express | REST PKI |
|----------------------------------------------------|-----------------------------------------|
| PKI_BRAZIL_CADES_ADR_BASICA | CADES_ICPBR_ADR_BASICA_WITHOUT_CRLS |
| PKI_BRAZIL_CADES_ADR_BASICA_WITH_REVOCATION_VALUES | CADES_ICPBR_ADR_BASICA |
| PKI_BRAZIL_CADES_ADR_TEMPO | CADES_ICPBR_ADR_TEMPO |
| PKI_BRAZIL_CADES_ADR_COMPLETA | CADES_ICPBR_ADR_COMPLETA |
| CADES_BES | CADES_BES_WITH_SIGNING_TIME_AND_NO_CRLS |
| CADES_BES_WITH_REVOCATION_VALUES | CADES_BES |
| CADES_T | Política ainda não existente |
| PADES_BASIC | PADES_BASIC_WITHOUT_LTV |
| PADES_BASIC_WITH_LTV | PADES_BASIC |
| PADES_BASIC_WITH_LTV | PADES_BASIC_WITH_ICPBR_CERTS |
| PADES_T | PADES_T_WITH_ICPBR_CERTS |
| NFE_PADRAO_NACIONAL | XML_ICPBR_NFE_PADRAO_NACIONAL |
| XADES_BES | XML_XADES_BES |
| XML_DSIG_BASIC | XML_DSIG_BASIC |
| PKI_BRAZIL_XML_ADR_BASICA | XML_ICPBR_ADR_BASICA |
| PKI_BRAZIL_XML_ADR_TEMPO | XML_ICPBR_ADR_TEMPO |
> **Observação**
>
> A política desejada ainda não existe no REST PKI? [Fale com a gente](https://www.lacunasoftware.com/pt/home/purchase).
# [Projeto de exemplos em PHP 5.5+](https://docs.lacunasoftware.com/articles/rest-pki/php/current)
O **Projeto de exemplos em PHP 5.5+** mostra como usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) em um projeto usando o **PHP 5.5+** (incluindo 7.x). Isto é
hospedado no GitHub em:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/php/plain
> **Observação**
>
> Para projetos destinados a versões anteriores do PHP, [clique aqui](https://docs.lacunasoftware.com/articles/rest-pki/php).
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou clonar o [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. No prompt de comando, navegue até a pasta `php/plain` e execute `composer install` para baixar as dependências (se você não tiver Composer instalado, pegue [aqui](https://getcomposer.org/))
1. Ainda na pasta `php/plain`, execute `composer start` para iniciar o servidor embutido do PHP
1. Abra http://localhost:8000 no navegador
## Mapa do projeto
Esta seção lista tem onde encontrar as partes relevantes em cada amostra de recurso no projeto.
### Autentificação com certificado digital
* [authentication-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/authentication-rest/index.php)
* JavaScript: [authentication-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/authentication-form.js)
* [authentication-rest/complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/authentication-rest/complete.php)
### Assinatura PAdES com arquivo já no servidor
* [pades-signature-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-rest/index.php)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-form.js)
* [pades-signature-rest/complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-rest/complete.php)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração em [upload.php]
(https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/upload.php)) é feito o fluxo de controle é o mesmo que na amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/php/current#pades),
mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/php/current#pades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `userfile` preenchidos.
### Marcas PAdES
Esse recurso é demonstrado como uma configuração opcional no [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/php/current#pades), amostra que por padrão começa comentada. Para ativá-lo, remova o comentário da linha a seguir [pades-signature-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-rest/index.php):
```php
array_push($signatureStarter->pdfMarks, getPdfMark(1));
```
> **Dica**
>
> Tente mudar o argumento para a função `getPdfMark()`para ver diferentes configurações de marcas PDF.
O código relevante está no arquivo [PadesVisualElementsRest.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/PadesVisualElementsRest.php),
função `getPdfMark()`.
### Assinatura PAdES usando chave do servidor
* [pades-server-key-express/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-server-key-express/index.php)
### Assinatura PAdES sem a comunicação com cliente
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura PAdES existente
* [open-pades-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-pades-rest/index.php)
### Versão para impressão
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/php/current#pades) está concluído e o link *Download a printer-friendly version of the signed file* é clicado, o fluxo
vai para o arquivo [printer-friendly-pades-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/printer-friendly-pades-rest/index.php), mas com parâmetro de URL `file` preenchidos.
O PDF gerado contém links para o arquivo [check.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/check.php), que
mostra detalhes das assinaturas.
### Assinatura CAdES com arquivo já no servidor
* [cades-signature-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-rest/index.php)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-form.js)
* [cades-signature-rest/complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-rest/complete.php)
### Assinatura CAdES com upload do arquivo pelo usuário
Depois que o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração no [upload.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/upload.php)) é feito
o fluxo de controle é o mesmo que na amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/php/current#cades), mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta CAdES
Após o fluxo de controle da amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/php/current#cades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `cmsfile` preenchidos.
### Assinatura CAdES usando chave do servidor
* [cades-signature-server-key.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/standard/cades-signature-server-key.php)
### Abrir/validar uma assinatura CAdES existente
* [open-cades-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-cades-rest/index.php)
### Assinatura XML do documento inteiro
* [xml-signature-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-signature-rest/index.php)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-form.js)
* [xml-signature-rest/complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-signature-rest/complete.php)
### Assinatura XML de um elemento
* [xml-nfe-signature-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-nfe-signature-rest/index.php)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-form.js)
* [xml-nfe-signature-rest/complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-nfe-signature-rest/complete.php)
### Assinatura XAdES de um elemento
Não está disponível neste projeto.
### Abrir/validar assinatura de um arquivo XML existente
* [open-xml-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-xml-rest/index.php)
### Lote de assinatura PAdES
* [batch-pades-signature-rest/index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/batch-pades-signature-rest/index.php)
* JavaScript: [batch-signature-rest-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/batch-signature-rest-form.js)
* Manipuladores AJAX: [batch-pades-signature-rest/start.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/batch-pades-signature-rest/start.php)
e [batch-pades-signature-rest/complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/batch-pades-signature-rest/complete.php)
### Lote otimizado de assinaturas PAdES
Não está disponível neste projeto.
### Lote de assinaturas CAdES
Não está disponível neste projeto.
### Lote de assinaturas XML de elementos no mesmo documento
* [batch-xml-element-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/standard/batch-xml-element-signature.php)
* JavaScript: [batch-xml-element-signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/standard/content/js/batch-xml-element-signature-form.js)
* AJAX handlers: [batch-xml-element-signature-start.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/standard/batch-xml-element-signature-start.php)
and [batch-xml-element-signature-complete.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/standard/batch-xml-element-signature-complete.php)
**Demonstrações e exemplos**
- Sign a PDF with a server key using Rest PKI: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-server-key-rest/index.php)
# [Projeto de exemplos em PHP 5.3/5.4](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy)
O **Projeto de exemplos em PHP 5.3/5.4** mostra como usar [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto usando **PHP 5.3** e **5.4**. Isto é hospedado no GitHub em:
https://github.com/LacunaSoftware/RestPkiSamples/tree/master/PHP/legacy
> **Observação**
>
> Somente usar este projeto se você não puder usar o [Projeto de exemplos em PHP 5.5+](https://docs.lacunasoftware.com/articles/rest-pki/php/current).
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/RestPkiSamples/archive/master.zip) ou clonar o [repositório](https://github.com/LacunaSoftware/RestPkiSamples.git)
1. No prompt de comando, navegue até a pasta `PHP/legacy` e execute o comando `composer install` e faça o download das dependências (se você não tiver Composer instalado, pegue [aqui](https://getcomposer.org/))
1. Configurar um site no seu servidor HTTP local apontando para à pasta `PHP/legacy`
1. Abrir o arquivo index.php no browser a URL correspondente (dependendo do passo anterior)
## Mapa do projeto
Esta seção lista tem onde encontrar as partes relevantes em cada amostra de recurso no projeto.
### Autentificação com certificado digital
* [authentication.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/authentication.php)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/content/js/signature-form.js)
* [authentication-action.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/authentication-action.php)
### Assinatura PAdES com arquivo já no servidor
* [pades-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/pades-signature.php)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/content/js/signature-form.js)
* [pades-signature-action.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/pades-signature-action.php)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração em [upload.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/upload.php))
é feito o fluxo de controle é o mesmo que na amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy#pades),
mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy#pades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `userfile` preenchidos.
### Marcas PAdES
Esse recurso é demonstrado como uma configuração opcional no [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy#pades), amostra que por padrão começa comentada. Para ativá-lo, remova o comentário da linha a seguir [pades-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/pades-signature.php):
```php
array_push($signatureStarter->pdfMarks, PadesVisualElements::getPdfMark(1));
```
> **Dica**
>
> Tente mudar o argumento para a função `getPdfMark()`para ver diferentes configurações de marcas PDF.
O código relevante está no arquivo [util-pades.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/util-pades.php),
função `getPdfMark()`.
### Assinatura PAdES usando chave do servidor
Não está disponível neste projeto.
### Assinatura PAdES sem a comunicação com cliente
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura PAdES existente
* [open-pades-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/open-pades-signature.php)
### Versão para impressão
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy#pades) está concluído e o link *Download a printer-friendly version of the signed file* é clicado, o fluxo
vai para o arquivo [printer-friendly-version.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/printer-friendly-version.php), mas com parâmetro de URL `file` preenchidos.
O PDF gerado contém links para o arquivo [check.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/check.php), que
mostra detalhes das assinaturas.
### Assinatura CAdES com arquivo já no servidor
* [cades-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/cades-signature.php)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/content/js/signature-form.js)
* [cades-signature-action.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/cades-signature-action.php)
### Assinatura CAdES com upload do arquivo pelo usuário
Depois que o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração no [upload.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/upload.php)) é feito
o fluxo de controle é o mesmo que na amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy#cades), mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta CAdES
Após o fluxo de controle da amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy#cades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `cmsfile` preenchidos.
### Assinatura CAdES usando chave do servidor
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura CAdES existente
* [open-cades-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/open-cades-signature.php)
### Assinatura XML do documento inteiro
* [xml-full-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/xml-full-signature.php)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/content/js/signature-form.js)
* [xml-full-signature-action.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/xml-full-signature-action.php)
### Assinatura XML de um elemento
* [xml-element-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/xml-element-signature.php)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/content/js/signature-form.js)
* [xml-element-signature-action.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/xml-element-signature-action.php)
### Assinatura XAdES de um elemento
Não está disponível neste projeto.
### Abrir/validar assinatura de um arquivo XML existente
Não está disponível neste projeto.
### Lote de assinatura PAdES
* [batch-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/batch-signature.php)
* JavaScript: [batch-signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/content/js/batch-signature-form.js)
* AJAX handlers: [batch-signature-start.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/batch-signature-start.php)
and [batch-signature-complete.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy/batch-signature-complete.php)
### Lote otimizado de assinaturas PAdES
Não está disponível neste projeto.
### Lote de assinaturas CAdES
Não está disponível neste projeto.
### Lote de assinaturas XML de elementos no mesmo documento
Não está disponível neste projeto.
# [Projeto de exemplos em PHP 5.2](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy52)
O **Projeto de exemplos em PHP 5.2** mostra como usar [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto usando **PHP 5.2**. Isto é hospedado no GitHub em:
https://github.com/LacunaSoftware/RestPkiSamples/tree/master/PHP/legacy52
> **Observação**
>
> Somente usar este projeto se você não puder usar o [Projeto de exemplos em PHP 5.5+](https://docs.lacunasoftware.com/articles/rest-pki/php/current) ou o [Projeto de exemplos em PHP 5.3/5.4](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy).
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/RestPkiSamples/archive/master.zip) ou clonar o [repositório](https://github.com/LacunaSoftware/RestPkiSamples.git)
1. No prompt de comando, navegue até a pasta `PHP/legacy52` e execute o comando `composer install` e faça o download das dependências (se você não tiver Composer instalado, pegue [aqui](https://getcomposer.org/))
1. Configurar um site no seu servidor HTTP local apontando para à pasta `PHP/legacy52`
1. Abrir o arquivo index.php no browser a URL correspondente (dependendo do passo anterior)
## Mapa do projeto
Esta seção lista tem onde encontrar as partes relevantes em cada amostra de recurso no projeto.
### Autentificação com certificado digital
* [authentication.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/authentication.php)
* JavaScript: (inside authentication.php)
* [authentication-action.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/authentication-action.php)
### Assinatura PAdES com arquivo já no servidor
* [pades-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/pades-signature.php)
* JavaScript: (inside pades-signature.php)
* [pades-signature-action.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/pades-signature-action.php)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração em [upload.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/upload.php)) é feito o fluxo de controle é o mesmo que na amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy52#pades),
mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy52#pades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `userfile` preenchidos.
### Marcas PAdES
Ainda não está disponível neste projeto
### Assinatura PAdES usando chave do servidor
Ainda não está disponível neste projeto.
### Assinatura PAdES sem a comunicação com cliente
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura PAdES existente
* [open-pades-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/open-pades-signature.php))
### Versão para impressão
Ainda não está disponível neste projeto.
### Assinatura CAdES com arquivo já no servidor
* [cades-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/cades-signature.php)
* JavaScript: (inside cades-signature.php)
* [cades-signature-action.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/cades-signature-action.php)
### Assinatura CAdES com upload do arquivo pelo usuário
Depois que o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração no [upload.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/upload.php)) é feito
o fluxo de controle é o mesmo que na amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy52#cades), mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta CAdES
Após o fluxo de controle da amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy52#cades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `cmsfile` preenchidos.
### Assinatura CAdES usando chave do servidor
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura CAdES existente
* [open-cades-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/open-cades-signature.php)
### Assinatura XML do documento inteiro
* [xml-full-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/xml-full-signature.php)
* JavaScript: (inside xml-full-signature.php)
* [xml-full-signature-action.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/xml-full-signature-action.php)
### Assinatura XML de um elemento
* [xml-element-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/xml-element-signature.php)
* JavaScript: (inside xml-element-signature.php)
* [xml-element-signature-action.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/xml-element-signature-action.php)
### Assinatura XAdES de um elemento
Ainda não está disponível neste projeto.
### Abrir/validar assinatura de um arquivo XML existente
Ainda não está disponível neste projeto.
### Lote de assinatura PAdES
* [batch-signature.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/batch-signature.php)
* JavaScript: [batch-signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/content/js/batch-signature-form.js)
* AJAX handlers: [batch-signature-start.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/batch-signature-start.php)
and [batch-signature-complete.php](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/PHP/legacy52/batch-signature-complete.php)
### Lote otimizado de assinaturas PAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas CAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas XML de elementos no mesmo documento
Ainda não está disponível neste projeto.
# [Usando o Rest PKI em Java](https://docs.lacunasoftware.com/articles/rest-pki/java)
Rest PKI pode ser usado em aplicações Java usando uma ampla gama de estruturas e plataformas. Para começar, escolha um dos projetos de amostras disponíveis:
* [Spring MVC (Java 7+)](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc)
* [Spring MVC (Java 6)](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6)
## Biblioteca de clientes
O uso de Rest PKI em aplicações Java deve ser feito usando um dos seguintes pacotes:
* Para Java 7+: [com.lacunasoftware.restpki:restpki-client](https://search.maven.org/artifact/com.lacunasoftware.restpki/restpki-client)
* Para Java 6: [com.lacunasoftware.restpki:restpki-client-java6](https://search.maven.org/artifact/com.lacunasoftware.restpki/restpki-client-java6)
### Referenciando o pacote (Java 7+)
As versões 1.15.9 e seguintes do `restpki-client` são compiladas para Java 8. Em Java 7, use a versão 1.15.7.
Se seu projeto usa **Maven**, adicione isso ao seu `pom.xml`:
```xml
...
com.lacunasoftware.restpki
restpki-client
1.15.11
...
```
Se seu projeto usa **Gradle**, adicione isso ao seu `build.gradle`:
```groovy
repositories {
mavenCentral()
}
dependencies {
implementation("com.lacunasoftware.restpki:restpki-client:1.15.11")
}
```
### Referenciando o pacote (Java 6)
Se seu projeto usa **Maven**, adicione isso ao seu `pom.xml`:
```xml
...
com.lacunasoftware.restpki
restpki-client-java6
1.9.0
...
```
Se seu projeto usa **Gradle**, adicione isso ao seu `build.gradle`:
```groovy
repositories {
mavenCentral()
}
dependencies {
implementation("com.lacunasoftware.restpki:restpki-client-java6:1.9.0")
}
```
## Referência API
Veja este pacote [javadoc](https://docs.lacunasoftware.com/en-us/content/javadocs/restpki-client/)
> **Observação**
>
> O javadoc é, na verdade, para a versão Java 7+ da biblioteca, mas as APIs de ambos os pacotes são quase idênticas.
## Código fonte
Os pacotes são de código aberto, hospedados em:
* Versão Java 7+: [BitBucket](https://bitbucket.org/Lacunas/restpki-java-client)
* Versão Java 6: [GitHub](https://github.com/LacunaSoftware/RestPkiJava6Client)
Sinta-se à vontade para distribuir os pacotes se precisar fazer alguma personalização.
# [Gerando códigos alfanuméricos em Java](https://docs.lacunasoftware.com/articles/rest-pki/java/alpha-codes)
Ao gerar uma versão para impressão de um arquivo assinado, é necessário gerar um *código de verificação* a ser
incluido no documento para que um terceiro que porventura receba o documento impresso possa visitar o seu site e fornecer o
código, obtendo assim a versão assinada digitalmente (que é a que efetivamente tem validade jurídica):

No passado, nós províamos o código-fonte para geração desse código de verificação como parte dos exemplos, a ser copiado
para o código da sua aplicação. Por exemplo:
```cs
public static class Util {
// ...
public static string GenerateVerificationCode() {
// String with exactly 32 letters and numbers to be used on the codes.
const string Alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
// Allocate a byte array large enough to receive the necessary entropy
var bytes = new byte[(int)Math.Ceiling(VerificationCodeSize * 5 / 8.0)];
// ...
return sb.ToString();
}
// ...
}
```
Entretanto, como o código de verificação desempenha um papel crucial na proteção do acesso aos seus documentos, nós
agora oferecemos a classe `AlphaCode` para fazer a geração dos códigos.
## Atualizando sua aplicação para usar a classe *AlphaCode*
> **Aviso**
>
> A lógica de geração de códigos de verificação passou por uma auditoria minuciosa e recebeu importantes melhorias.
> Por isso, recomendamos que você atualize a sua aplicação para utilizar a classe *AlphaCode* ao invés de utilizar
> o código-fonte antigo.
Você provavelmente copiou anteriormente os métodos (agora obsoletos) `generateVerificationCode`, `formatVerificationCode` e `parseVerificationCode`
dos exemplos para o seu código. Para atualizar a sua aplicação:
1. Atualize o pacote de Maven *com.lacunasoftware.restpki* para a versão **1.11.0** ou superior
1. Substitua a implementação desses métodos no seu código por chamadas aos métodos da classe *AlphaCode*:
```java
public static String generateVerificationCode() {
return AlphaCode.generate();
}
public static String formatVerificationCode(String code) {
return AlphaCode.format(code);
}
public static String parseVerificationCode(String formattedCode) {
return AlphaCode.parse(formattedCode);
}
```
## Princípios de projeto
A classe *AlphaCode* gera códigos alfanuméricos feitos para serem lidos por pessoas seguindo os princípios abaixo:
1. Os códigos devem de fácil leitura
1. Os códigos devem poder ser facilmente digitados com baixo risco de confundir caracteres similares como `O` e `0`
1. Os códigos devem ter relativa alta entropia com relação ao tamanho do código (alto número de possíveis códigos relativo ao tamanho
do código, permitindo ao desenvolvedor escolher um código de tamanho relativamente pequeno)
Para melhorar a legibilidade, são utilizadas apenas letras maiúsculas e não são utilizados os caracteres `O`, `0`, `1` e `I`. A entropia
por caractere é relativamente alta, se comparada com um código hexadecimal: como existem 32 possíveis caracteres, cada caractere contribui
com 5 bits à entropia total do código gerado (25% a mais do que num código hexadecimal), o que resulta em códigos menores para uma dada entropia mínima.
Por exemplo, para gerar um código com 80 bits de entropia (2^80 possíveis códigos):
* Em hexadecimal, seria necessário um código de 20 caracteres, ex: `90A0-F20F-5883-8D55-AD31`
* Com a classe *AlphaCode*, o código teria apenas 16 caracteres, ex: `FFWC-RHC5-9NLF-VM42`
## Veja também
* [Gerando códigos alfanuméricos em Java (PKI Express)](https://docs.lacunasoftware.com/articles/pki-express/java/alpha-codes)
* [Usando o Rest PKI em Java](https://docs.lacunasoftware.com/articles/rest-pki/java)
**Demonstrações e exemplos**
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
- Make a printer-friendly version of a signed PDF using Rest PKI: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PrinterFriendlyPadesRestController.java)
# [Como converter do PKI Express para o REST PKI em Java](https://docs.lacunasoftware.com/articles/rest-pki/java/convert-from-express)
Este artigo explica as alterações necessárias para converter uma aplicação Java utilizando [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) para uma aplicação Java que utilize [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki).
## Equivalência de políticas
Ao migrar sua aplicação Java que utiliza o PKI Express para utilizar o REST PKI, é importante se atentar aos nomes das políticas usadas na aplicação.
Na tabela a seguir, é possível encontrar as equivalências entre os nomes das políticas em ambas bibliotecas. Na primeira coluna temos os nomes das políticas na biblioteca PKI Express, e na segunda coluna temos os nomes das mesmas políticas só que na biblioteca REST PKI.
| PKI Express | REST PKI |
|---------------------------------------------|----------------------------------|
| PadesBasicWithLTV | PadesBasic |
| PadesBasicWithLTV | PadesBasicWithPkiBrazilCerts |
| PadesBasic | Política ainda não existente |
| PadesT | PadesTWithPkiBrazilCerts |
| CadesBesWithRevocationValues | CadesBes |
| CadesBes | CadesBesWithSigningTimeAndNoCrls |
| CadesT | Política ainda não existente |
| PkiBrazilCadesAdrBasicaWithRevocationValues | PkiBrazilAdrBasica |
| PkiBrazilCadesAdrBasica | Política ainda não existente |
| PkiBrazilCadesAdrTempo | PkiBrazilAdrTempo |
| PkiBrazilCadesAdrCompleta | PkiBrazilAdrCompleta |
| XadesBes | XadesBasic |
| XmlDSigBasic | XmlDSigBasic |
| PkiBrazilXadesAdrBasica | PkiBrazilXadesAdrBasica |
| PkiBrazilXadesAdrTempo | PkiBrazilXadesAdrTempo |
| NFePadraoNacional | NFePadraoNacional |
| CodWithSHA1 | CodSha1 |
| CodWithSHA256 | CodSha256 |
> **Observação**
>
> A política desejada ainda não existe no REST PKI? [Fale com a gente](https://www.lacunasoftware.com/pt/home/purchase).
# [Projeto de exemplos em Java 6 com Spring MVC](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6)
O **Projeto de exemplos em Java 6 com Spring MVC** mostra como usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) no **Java 6** em uma aplicação web usando
framework [Spring MVC](http://spring.io/). É hospedado no GiHub em:
https://github.com/LacunaSoftware/RestPkiSamples/tree/master/Java/spring-mvc-java6
> **Observação**
>
> Este projeto é somente para **Java 6**. Para Java 7+, veja o projeto [Spring MVC](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc).
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/RestPkiSamples/archive/master.zip) ou clonar o [repositório](https://github.com/LacunaSoftware/RestPkiSamples.git)
1. Gere um token de acesso à API no [REST PKI website](https://pki.rest/)
1. Cole seu token de acesso no arquivo [Java/spring-mvc-java6/src/main/resources/application.properties](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/application.properties#L27-L30)
1. No prompt de comando, navegue até a pasta `Java/sample-spring-java6` e execute o comando `gradlew run` (no Linux `./gradlew run`). Se você estiver usando Windows, você pode, alternativamente, clicar duas vezes no arquivo `Run-Sample.bat`.
1. Depois de ver a mensagem *"Starded Application in x.xxx seconds"* (a porcentagem na tela não atingirá 100%), abra o browser e vá para a URL http://localhost:60963
## Mapa do projeto
Esta seção lista tem onde encontrar as partes relevantes em cada amostra de recurso no projeto.
### Autentificação com certificado digital
* Controller: [AuthenticationController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/AuthenticationController.java)
* Views:
* [authentication.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/authentication.html)
(Javascript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/static/js/signature-form.js))
* [authentication-success.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/authentication-success.html)
* [authentication-failed.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/authentication-failed.html)
### Assinatura PAdES com arquivo já no servidor
* Controller: [PadesSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/PadesSignatureController.java)
* Views:
* [pades-signature.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/pades-signature.html)
(Javascript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/static/js/signature-form.js))
* [pades-signature-info.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/pades-signature-info.html)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração em [UploadController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/UploadController.java)) é feito o fluxo de controle é o mesmo que na amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#pades),
mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#pades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `userfile` preenchidos.
### Marcas PAdES
Esse recurso é demonstrado como uma configuração opcional no [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#pades), amostra que por padrão começa comentada. Para ativá-lo, remova o
comentário da linha a seguir [PadesSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/PadesSignatureController.java):
```java
signatureStarter.addPdfMark(PadesVisualElements.getPdfMark(1));
```
> **Dica**
>
> Tente mudar o argumento para a função `getPdfMark()`para ver diferentes configurações de marcas PDF.
O código relevante está no arquivo [PadesVisualElements](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/util/PadesVisualElements.java),
função `getPdfMark()`.
### Assinatura PAdES usando chave do servidor
Ainda não está disponível neste projeto.
### Assinatura PAdES sem a comunicação com cliente
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura PAdES existente
* Controller: [OpenPadesSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/OpenPadesSignatureController.java)
* View: [open-pades-signature.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/open-pades-signature.html)
### Versão para impressão
Ainda não está disponível neste projeto.
### Assinatura CAdES com arquivo já no servidor
* Controller: [CadesSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/CadesSignatureController.java)
* Views:
* [cades-signature.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/cades-signature.html)
(Javascript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/static/js/signature-form.js))
* [cades-signature-info.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/cades-signature-info.html)
### Assinatura CAdES com upload do arquivo pelo usuário
Depois que o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração no [UploadController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/UploadController.java) é feito
o fluxo de controle é o mesmo que na amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#cades), mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta CAdES
Após o fluxo de controle da amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#cades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `cmsfile` preenchidos.
### Assinatura CAdES usando chave do servidor
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura CAdES existente
* Controller: [OpenCadesSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/OpenCadesSignatureController.java)
* View: [open-cades-signature.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/open-cades-signature.html)
### Assinatura XML do documento inteiro
* Controller: [XmlSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/XmlSignatureController.java)
* Views:
* [xml-full-signature.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/xml-full-signature.html)
(Javascript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/static/js/signature-form.js))
* [xml-signature-info.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/xml-signature-info.html)
### Assinatura XML de um elemento
* Controller: [XmlSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/XmlSignatureController.java)
* Views:
* [xml-element-signature.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/xml-element-signature.html)
(Javascript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/static/js/signature-form.js))
* [xml-signature-info.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/xml-signature-info.html)
### Assinatura XAdES de um elemento
Ainda não está disponível neste projeto.
### Abrir/validar assinatura de um arquivo XML existente
Ainda não está disponível neste projeto.
### Lote de assinatura PAdES
* Controller: [BatchSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/java/sample/controller/BatchSignatureController.java)
* View: [batch-signature.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/templates/batch-signature.html)
* Javascript: [batch-signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/spring-mvc-java6/src/main/resources/static/js/batch-signature-form.js)
### Lote otimizado de assinaturas PAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas CAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas XML de elementos no mesmo documento
Ainda não está disponível neste projeto.
# [Projeto de exemplos em Java 7+ com Spring MVC](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc)
O **Projeto de exemplos em Java 7+ com Spring MVC** mostra como usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) em uma aplicação web Java 7+ usando framework [Spring MVC](http://spring.io/). É hospedado no GiHub em:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/java/springmvc
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou clonar o [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. No prompt de comando, navegue até a pasta `java/springmvc` e execute o comando `gradlew run` (no Linux `./gradlew run`). Se você estiver usando Windows, você pode, alternativamente, clicar duas vezes no arquivo `Run-Sample.bat`.
1. Depois de ver a mensagem *"Started Application in x.xxx seconds"* (a porcentagem na tela não atingirá 100%), abra o browser e vá para a URL http://localhost:60963
## Mapa do projeto
Esta seção lista tem onde encontrar as partes relevantes em cada amostra de recurso no projeto.
### Autentificação com certificado digital
* Controller: [AuthenticationRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationRestController.java)
* Views:
* [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/authentication-rest/index.html)
(Javascript em [authentication-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/authentication-form.js))
* [success.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/authentication-rest/success.html)
* [failed.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/authentication-rest/failed.html)
### Assinatura PAdES com arquivo já no servidor
* Controller: [PadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureRestController.java)
* Views:
* [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-rest/index.html)
(Javascript em [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-form.js))
* [signature-info.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-rest/signature-info.html)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração em [UploadController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/UploadController.java)) e da view [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/upload/index.html), o fluxo de controle é o mesmo que na amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#pades),
mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#pades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `userfile` preenchidos.
### Marcas PAdES
Esse recurso é demonstrado como uma configuração opcional no [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#pades), amostra que por padrão começa comentada. Para ativá-lo, remova o
comentário da linha a seguir [PadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureRestController.java):
```java
signatureStarter.addPdfMark(PadesVisualElements.getPdfMark(1));
```
> **Dica**
>
> Tente mudar o argumento para a função `getPdfMark()`para ver diferentes configurações de marcas PDF.
O código relevante está no arquivo [PadesVisualElements](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/util/restpki/PadesVisualElements.java),
função `getPdfMark()`.
### Assinatura PAdES usando chave do servidor
Este exemplo não está neste projeto. Use o do repositório antigo [RestPkiSamples](https://github.com/LacunaSoftware/RestPkiSamples/tree/master/Java/sample-spring-mvc):
* Controller: [PadesSignatureServerKeyController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/sample-spring-mvc/src/main/java/sample/controller/PadesSignatureServerKeyController.java)
* View: [pades-signature-server-key.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/sample-spring-mvc/src/main/resources/templates/pades-signature-server-key.html)
### Assinatura PAdES sem a comunicação com cliente
* Ainda não está disponível neste projeto, mas você pode consultar esta implementação antiga no [Rest PKI Samples](https://github.com/LacunaSoftware/RestPkiSamples):
* Controller: [PadesSignatureWithoutDirectCommunicationControlller](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Controllers/PadesSignatureWithoutDirectCommunicationController.cs)
* Views:
* [PadesSignatureWithoutDirectCommunication/Index.cshtml](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Views/PadesSignatureWithoutDirectCommunication/Index.cshtml)
(JavaScript em [signature-start-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Content/js/signature-start-form.js))
* [PadesSignatureWithoutDirectCommunication/Complete.cshtml](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Views/PadesSignatureWithoutDirectCommunication/Complete.cshtml)
(JavaScript em [signature-complete-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Content/js/signature-complete-form.js))
* [PadesSignatureWithoutDirectCommunication/SignatureInfo.cshtml](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Views/PadesSignatureWithoutDirectCommunication/SignatureInfo.cshtml)
### Abrir/validar uma assinatura PAdES existente
* Controller: [OpenPadesRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenPadesRestController.java)
* View: [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/open-pades-rest/index.html)
### Versão para impressão
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#pades) estar concluído e o link *Download a printer-friendly version of the signed file* ser clicado, o fluxo
vai para o controller [PrinterFriendlyPadesRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PrinterFriendlyPadesRestController.java) com o parâmetro de URL `fileId` preenchido.
### Assinatura CAdES com arquivo já no servidor
* Controller: [CadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/CadesSignatureRestController.java)
* Views:
* [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/cades-signature-rest/index.html)
(Javascript em [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-form.js))
* [signature-info.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/cades-signature-rest/signature-info.html)
### Assinatura CAdES com upload do arquivo pelo usuário
Depois que o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração no [UploadController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/UploadController.java)) e na view [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/upload/index.html) é feito,
o fluxo de controle é o mesmo que na amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#cades), mas com o parâmetro de URL `userfile` preenchido.
### Assinatura conjunta CAdES
Após o fluxo de controle da amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#cades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `cmsfile` preenchidos.
### Assinatura CAdES usando chave do servidor
Este exemplo não está neste projeto. Use o do repositório antigo [RestPkiSamples](https://github.com/LacunaSoftware/RestPkiSamples/tree/master/Java/sample-spring-mvc):
* Controller: [CadesSignatureServerKeyController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/sample-spring-mvc/src/main/java/sample/controller/CadesSignatureServerKeyController.java)
* View: [cades-signature-server-key.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/sample-spring-mvc/src/main/resources/templates/cades-signature-server-key.html)
### Abrir/validar uma assinatura CAdES existente
Este exemplo não está neste projeto. Use o do repositório antigo [RestPkiSamples](https://github.com/LacunaSoftware/RestPkiSamples/tree/master/Java/sample-spring-mvc):
* Controller: [OpenCadesSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/sample-spring-mvc/src/main/java/sample/controller/OpenCadesSignatureController.java)
* View: [open-cades-signature.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/sample-spring-mvc/src/main/resources/templates/open-cades-signature.html)
### Assinatura XML do documento inteiro
* Controller: [XmlSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/XmlSignatureRestController.java)
* Views:
* [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/xml-signature-rest/index.html)
(Javascript em [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-form.js))
* [signature-info.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/xml-signature-rest/signature-info.html)
### Assinatura XML de um elemento
* Controller: [XmlNFeSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/XmlNFeSignatureRestController.java)
* Views:
* [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/xml-nfe-signature-rest/index.html)
(Javascript em [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-form.js))
* [signature-info.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/xml-nfe-signature-rest/signature-info.html)
### Assinatura XAdES de um elemento
Ainda não está disponível neste projeto.
### Abrir/validar assinatura de um arquivo XML existente
* Controller: [OpenXmlRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenXmlRestController.java)
* View: [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/open-xml-rest/index.html)
### Lote de assinatura PAdES
* Controller: [BatchPadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchPadesSignatureRestController.java)
* View: [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/batch-signature-rest/index.html)
* Javascript: [batch-signature-rest-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/batch-signature-rest-form.js)
### Lote otimizado de assinaturas PAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas CAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas XML de elementos no mesmo documento
Ainda não está disponível neste projeto.
**Demonstrações e exemplos**
- Sign a Brazilian digital diploma XML using Rest PKI: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/XmlDiplomaSignatureRestController.java)
- Sign a medical prescription (Receita Simples) using Rest PKI: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ReceitaSimplesRestController.java)
# [Usando o Rest PKI em .NET](https://docs.lacunasoftware.com/articles/rest-pki/dotnet)
Rest PKI pode ser usado em aplicações .NET (C#, VB.NET, C++.NET) usando uma ampla gama de tecnologias e plataformas. Para começar, escolha uma dos exemplos de projetos disponíveis:
* [ASP.NET MVC](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc)
* [ASP.NET Web Forms](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms)
* [ASP.NET Core](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore)
## Biblioteca do cliente
Todos os projetos de exemplo em .NET usam o pacote [Lacuna.RestPki.Client](https://www.nuget.org/packages/Lacuna.RestPki.Client/) do NuGet para acessar o Rest PKI.
### Versões e plataformas suportadas
O pacote tem duas linhas de versões, sem nenhuma versão entre a 1.24.1 e a 2.2.0:
| Versões | Frameworks de destino |
| --- | --- |
| 1.24.1, a última versão 1.x | .NET Framework 3.5, 4.0, 4.5 e 4.6; .NET Standard 1.3 e 2.0 |
| 2.2.0 e seguintes | Só .NET Standard 2.0 |
Desde a 2.2.0, o pacote é publicado junto com o [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core): os números de versão dele são os mesmos
do pacote cliente do Rest PKI Core, o `Lacuna.RestPkiCore.Client`, que depende dele. As duas linhas têm as mesmas classes e enviam as
mesmas requisições à API do Rest PKI, então as duas funcionam com o pki.rest. Escolha pelo framework de destino da sua aplicação:
* **.NET Framework 4.6.1 ou superior, .NET Core 2.0 ou superior ou .NET 5 ou superior** (plataformas que implementam o .NET Standard
2.0): use a versão mais recente.
* **.NET Framework 3.5 a 4.6, ou uma plataforma que implementa só o .NET Standard 1.3**: use a 1.24.1.
Se a sua aplicação também usa o `Lacuna.RestPkiCore.Client` 2.2.0 ou superior, mantenha os dois pacotes na mesma versão: o cliente do
Rest PKI Core exige no mínimo a própria versão do `Lacuna.RestPki.Client`.
O que muda depois da 1.24.1: desde a 2.2.0, `RestPkiException.ErrorCodeStr` traz o código de erro como texto, inclusive para códigos
que o enum `ErrorCodes` não lista. A 5.0.0 acrescenta um construtor do `RestPkiClient` que recebe tags de requisição, um recurso do
Rest PKI Core que o pki.rest ignora, e o construtor que recebe um `Uri` deixa de ser público; por isso, passe o endpoint como string.
As dependências do pacote são de propriedade da Microsoft ou da própria Lacuna Software. Nenhum pacote de terceiros é necessário (exceto o pacote
[Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/), o padrão de fato para manipular JSON no .NET).
Veja este pacote [referência API](https://docs.lacunasoftware.com/api/Lacuna.RestPki.Client).
## Erros
Toda exceção que o cliente lança deriva de `RestException`:
| O que aconteceu | Exceção | O que ela informa |
| --- | --- | --- |
| O Rest PKI devolveu HTTP 422 com o código de erro `ValidationError`: um certificado não passou na validação | `ValidationException` | `ValidationResults` |
| O Rest PKI devolveu HTTP 422 com outro código de erro, como `SecurityContextRequired` | `RestPkiException` | `ErrorCode`, `Detail` |
| O Rest PKI devolveu HTTP 401 (o token de acesso não é válido), 500 ou outro código de status de erro | `RestErrorException` | `StatusCode`, `ErrorMessage` |
| Nenhuma resposta chegou: falha de rede, erro de resolução de nome ou de TLS, ou timeout | `RestUnreachableException` | `InnerException`, a causa |
`ValidationException` e `RestPkiException` ficam no namespace `Lacuna.RestPki.Client`; `RestException`, `RestErrorException` e
`RestUnreachableException` ficam em `Lacuna.RestClient`, do pacote `Lacuna.RestClient`, uma dependência deste. Para HTTP 400, 403 e
404, o `Lacuna.RestClient` lança uma subclasse mais específica de `RestException` quando o corpo da resposta descreve o erro. Para os
códigos de status e os códigos de erro, veja [Códigos de retorno](https://docs.lacunasoftware.com/articles/rest-pki/return-codes).
# [Gerando códigos alfanuméricos em .NET](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/alpha-codes)
Ao gerar uma versão para impressão de um arquivo assinado, é necessário gerar um *código de verificação* a ser
incluido no documento para que um terceiro que porventura receba o documento impresso possa visitar o seu site e fornecer o
código, obtendo assim a versão assinada digitalmente (que é a que efetivamente tem validade jurídica):

No passado, nós províamos o código-fonte para geração desse código de verificação como parte dos exemplos, a ser copiado
para o código da sua aplicação. Por exemplo:
```cs
public static class Util {
// ...
public static string GenerateVerificationCode() {
// String with exactly 32 letters and numbers to be used on the codes.
const string Alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
// Allocate a byte array large enough to receive the necessary entropy
var bytes = new byte[(int)Math.Ceiling(VerificationCodeSize * 5 / 8.0)];
// ...
return sb.ToString();
}
// ...
}
```
Entretanto, como o código de verificação desempenha um papel crucial na proteção do acesso aos seus documentos, nós
agora oferecemos a classe `AlphaCode` para fazer a geração dos códigos.
## Atualizando sua aplicação para usar a classe *AlphaCode*
> **Aviso**
>
> A lógica de geração de códigos de verificação passou por uma auditoria minuciosa e recebeu importantes melhorias.
> Por isso, recomendamos que você atualize a sua aplicação para utilizar a classe *AlphaCode* ao invés de utilizar
> o código-fonte antigo.
Você provavelmente copiou anteriormente os métodos (agora obsoletos) `GenerateVerificationCode`, `FormatVerificationCode` e `ParseVerificationCode`
dos exemplos para o seu código. Para atualizar a sua aplicação:
1. Atualize o pacote de nuget [Lacuna.RestPki.Client](https://www.nuget.org/packages/Lacuna.RestPki.Client) para a versão **1.18.0** ou superior
1. Substitua a implementação desses métodos no seu código por chamadas aos métodos da classe *AlphaCode*:
```cs
public static string GenerateVerificationCode() => AlphaCode.Generate();
public static string FormatVerificationCode(string code) => AlphaCode.Format(code);
public static string ParseVerificationCode(string formattedCode) => AlphaCode.Parse(formattedCode);
```
## Princípios de projeto
A classe *AlphaCode* gera códigos alfanuméricos feitos para serem lidos por pessoas seguindo os princípios abaixo:
1. Os códigos devem de fácil leitura
1. Os códigos devem poder ser facilmente digitados com baixo risco de confundir caracteres similares como `O` e `0`
1. Os códigos devem ter relativa alta entropia com relação ao tamanho do código (alto número de possíveis códigos relativo ao tamanho
do código, permitindo ao desenvolvedor escolher um código de tamanho relativamente pequeno)
Para melhorar a legibilidade, são utilizadas apenas letras maiúsculas e não são utilizados os caracteres `O`, `0`, `1` e `I`. A entropia
por caractere é relativamente alta, se comparada com um código hexadecimal: como existem 32 possíveis caracteres, cada caractere contribui
com 5 bits à entropia total do código gerado (25% a mais do que num código hexadecimal), o que resulta em códigos menores para uma dada entropia mínima.
Por exemplo, para gerar um código com 80 bits de entropia (2^80 possíveis códigos):
* Em hexadecimal, seria necessário um código de 20 caracteres, ex: `90A0-F20F-5883-8D55-AD31`
* Com a classe *AlphaCode*, o código teria apenas 16 caracteres, ex: `FFWC-RHC5-9NLF-VM42`
## Veja também
* [Gerando códigos alfanuméricos (PKI SDK)](https://docs.lacunasoftware.com/articles/pki-sdk/alpha-codes)
* [Usando o Rest PKI em .NET](https://docs.lacunasoftware.com/articles/rest-pki/dotnet)
**Demonstrações e exemplos**
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
- Make a printer-friendly version of a signed PDF using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyPadesRestController.cs)
# [Projeto de exemplos em ASP.NET MVC](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc)
O **Projeto de exemplos em ASP.NET MVC** mostra como usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto usando [ASP.NET MVC 5](https://docs.microsoft.com/en-us/aspnet/mvc/mvc5). É hospedado no GiHub em:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/dotnet/mvc
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou clonar o [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Abra a pasta do projeto (`dotnet\mvc`)
1. Abra o arquivo de solução (.sln) no Visual Studio
1. Execute a solução. Certifique-se de que seu sistema permita a restauração automática do pacote Nuget (se isso não ocorrer, restaure manualmente os pacotes).
## Mapa do projeto
Esta seção lista tem onde encontrar as partes relevantes em cada amostra de recurso no projeto.
### Autentificação com certificado digital
* Controller: [AuthenticationRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/AuthenticationRestController.cs)
* Views:
* [AuthenticationRest/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/AuthenticationRest/Index.cshtml)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Content/js/signature-form.js))
* [AuthenticationRest/Failed.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/AuthenticationRest/Failed.cshtml)
* [AuthenticationRest/Success.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/AuthenticationRest/Success.cshtml)
### Assinatura PAdES com arquivo já no servidor
* Controller: [PadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestController.cs)
* Views:
* [PadesSignatureRest/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/PadesSignatureRest/Index.cshtml)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Content/js/signature-form.js))
* [PadesSignatureRest/SignatureInfo.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/PadesSignatureRest/SignatureInfo.cshtml)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração em [UploadController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/UploadController.cs) e
[Upload/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/Upload/Index.cshtml)) é feito o fluxo de controle é o mesmo que na amostra [PAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#pades),
mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#pades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `userfile` preenchidos.
### Marcas PAdES
Esse recurso é demonstrado como uma configuração opcional no [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#pades), amostra que por padrão começa comentada. Para ativá-lo, remova o
comentário da linha a seguir [PadesSignatureController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestController.cs):
```cs
signatureStarter.PdfMarks.Add(PadesVisualElements.GetPdfMark(1));
```
> **Dica**
>
> Tente mudar o argumento para a função `getPdfMark()`para ver diferentes configurações de marcas PDF.
O código relevante está no arquivo [PadesVisualElements](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Classes/PadesVisualElements.cs),
função `getPdfMark()`.
### Assinatura PAdES sem a comunicação com cliente
* Controller: [PadesSignatureWithoutDirectCommunicationControlller](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Controllers/PadesSignatureWithoutDirectCommunicationController.cs)
* Views:
* [PadesSignatureWithoutDirectCommunication/Index.cshtml](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Views/PadesSignatureWithoutDirectCommunication/Index.cshtml)
(JavaScript on [signature-start-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Content/js/signature-start-form.js))
* [PadesSignatureWithoutDirectCommunication/Complete.cshtml](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Views/PadesSignatureWithoutDirectCommunication/Complete.cshtml)
(JavaScript on [signature-complete-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Content/js/signature-complete-form.js))
* [PadesSignatureWithoutDirectCommunication/SignatureInfo.cshtml](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Views/PadesSignatureWithoutDirectCommunication/SignatureInfo.cshtml)
### Abrir/validar uma assinatura PAdES existente
* Controller: [OpenPadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenPadesSignatureRestController.cs)
* View: [OpenPadesSignatureRest/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/OpenPadesSignatureRest/Index.cshtml)
### Versão para impressão
* Controller: [PrinterFriendlyPadesRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyPadesRestController.cs)
### Assinatura CAdES com arquivo já no servidor
* Controller: [CadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesSignatureRestController.cs)
* Views:
* [CadesSignatureRest/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/CadesSignatureRest/Index.cshtml)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Content/js/signature-form.js))
* [CadesSignatureRest/SignatureInfo.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/CadesSignatureRest/SignatureInfo.cshtml)
### Assinatura CAdES com upload do arquivo pelo usuário
Depois que o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração no [UploadController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/UploadController.cs)
e
[Upload/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/Upload/Index.cshtml)) é feito
o fluxo de controle é o mesmo que na amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#cades), mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta CAdES
Após o fluxo de controle da amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#cades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `cmsfile` preenchidos.
### Abrir/validar uma assinatura CAdES existente
* Controller: [OpenCadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenCadesSignatureRestController.cs)
* View: [OpenCadesSignatureRest/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/OpenCadesSignatureRest/Index.cshtml)
### Assinatura XML do documento inteiro
* Controller: [XmlSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlSignatureRestController.cs)
* Views:
* [XmlSignatureRest/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/XmlSignatureRest/Index.cshtml)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Content/js/signature-form.js))
* [XmlSignatureRest/SignatureInfo.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/XmlSignatureRest/SignatureInfo.cshtml)
### Assinatura XML de um elemento
* Controller: [XmlNFeSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlNFeSignatureRestController.cs)
* Views:
* [XmlNFeSignatureRest/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/XmlNFeSignatureRest/Index.cshtml)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Content/js/signature-form.js))
* [XmlNFeSignatureRest/SignatureInfo.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/XmlNFeSignatureRest/SignatureInfo.cshtml)
### Abrir/validar assinatura de um arquivo XML existente
* Controller: [OpenXmlSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenXmlSignatureRestController.cs)
* View: [OpenXmlSignatureRest/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/OpenXmlSignatureRest/Index.cshtml)
### Lote de assinatura PAdES
* Controller: [BatchPadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/BatchPadesSignatureRestController.cs)
* View: [BatchPadesSignatureRest/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/BatchPadesSignatureRest/Index.cshtml)
(JavaScript on [batch-signature-rest-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Content/js/batch-signature-rest-form.js))
### Lote otimizado de assinaturas PAdES
* Controller: [BatchSignatureOptimizedController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Controllers/BatchSignatureOptimizedController.cs)
* View: [BatchSignatureOptimized/Index.cshtml](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Views/BatchSignatureOptimized/Index.cshtml)
(JavaScript on [batch-signature-optimized-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/MVC/SampleSite/Content/js/batch-signature-optimized-form.js))
### Lote de assinaturas CAdES
* Controller: [BatchCadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/BatchCadesSignatureRestController.cs)
* View: [BatchCadesSignatureRest/Index.cshtml](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Views/BatchCadesSignatureRest/Index.cshtml)
(JavaScript on [batch-signature-rest-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Content/js/batch-signature-rest-form.js))
### Lote de assinaturas XML de elementos no mesmo documento
Ainda não está disponível neste projeto.
**Demonstrações e exemplos**
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Sign a PDF with Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestController.cs)
- Sign multiple PDFs with Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/BatchPadesSignatureRestController.cs)
- Sign a file with CAdES using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesSignatureRestController.cs)
- Sign multiple files with CAdES using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/BatchCadesSignatureRestController.cs)
- Sign an XML document using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlSignatureRestController.cs)
- Sign one element of an XML document using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlContractSignatureRestController.cs)
- Sign a Brazilian NF-e using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlNFeSignatureRestController.cs)
- Sign a COD XML using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlCodSignatureRestController.cs)
- Validate a signed PDF using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenPadesSignatureRestController.cs)
- Validate a CMS/CAdES signature using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenCadesSignatureRestController.cs)
- Validate a signed XML using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenXmlSignatureRestController.cs)
- Make a printer-friendly version of a signed PDF using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyPadesRestController.cs)
- Make a printer-friendly version of a CMS/CAdES signature using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyCadesRestController.cs)
- Authenticate with a digital certificate using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/AuthenticationRestController.cs)
# [Projeto de exemplos em ASP.NET Core](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore)
O **Projeto de exemplos em ASP.NET Core** mostra como usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto usando [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/). É hospedado no GiHub em:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/dotnet/spa
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou clonar o [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Abra a pasta do projeto (`dotnet\spa\PkiSuiteAspNetSpaSample`)
1. Abra o arquivo de solução (.sln) no Visual Studio
1. Execute a solução. Certifique-se de que seu sistema permita a restauração automática do pacote Nuget (se isso não ocorrer, restaure manualmente os pacotes).
## Mapa do projeto
Esta seção lista tem onde encontrar as partes relevantes em cada amostra de recurso no projeto.
### Autentificação com certificado digital
* API Controller: [AuthenticationRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/Controllers/AuthenticationRestController.cs)
* Componente SPA (TypeScript): [authentication-rest.component.ts](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/authentication-rest/authentication-rest.component.ts)
* Template SPA (HTML): [authentication-rest.component.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/authentication-rest/authentication-rest.component.html)
### Assinatura PAdES com arquivo já no servidor
* API Controller: [PadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/Controllers/PadesSignatureRestController.cs)
* Componente SPA (TypeScript): [pades-signature-rest.component.ts](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/pades-signature-rest/pades-signature-rest.component.ts)
* Template SPA (HTML): [pades-signature-rest.component.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/pades-signature-rest/pades-signature-rest.component.html)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração em [upload.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/AspNetCore/CoreWebApp/wwwroot/controllers/upload.js),
template [upload.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/AspNetCore/CoreWebApp/wwwroot/views/upload.html)
and API Controller [UploadController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/AspNetCore/CoreWebApp/Controllers/UploadController.cs)) é feito o fluxo de controle é o mesmo que na amostra [PAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#pades),
mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#pades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `userfile` preenchidos.
### Marcas PAdES
Esse recurso é demonstrado como uma configuração opcional no [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#pades), amostra que por padrão começa comentada. Para ativá-lo, remova o
comentário da linha a seguir [PadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/Controllers/PadesSignatureRestController.cs):
```cs
signatureStarter.PdfMarks.Add(PadesVisualElements.GetPdfMark(storage, 1));
```
> **Dica**
>
> Tente mudar o argumento para a função `getPdfMark()`para ver diferentes configurações de marcas PDF.
O código relevante está no arquivo [PadesVisualElements](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/Classes/PadesVisualElements.cs),
função `getPdfMark()`.
### Assinatura PAdES usando chave do servidor
Ainda não está disponível neste projeto.
### Assinatura PAdES sem a comunicação com cliente
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura PAdES existente
* API Controller: [OpenPadesRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/Controllers/OpenPadesRestController.cs)
* Componente SPA (TypeScript): [open-pades-rest.component.ts](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/open-pades-rest/open-pades-rest.component.ts)
* Template SPA (HTML): [open-pades-rest.component.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/open-pades-rest/open-pades-rest.component.html)
### Versão para impressão
Ainda não está disponível neste projeto.
### Assinatura CAdES com arquivo já no servidor
* API Controller: [CadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/Controllers/CadesSignatureRestController.cs)
* Componente SPA (TypeScript): [cades-signature-rest.component.ts](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/cades-signature-rest/cades-signature-rest.component.ts)
* Template SPA (HTML): [cades-signature-rest.component.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/cades-signature-rest/cades-signature-rest.component.html)
### Assinatura CAdES com upload do arquivo pelo usuário
* Ainda não está disponível neste projeto, mas você pode consultar esta implementação antiga no [Rest PKI Samples](https://github.com/LacunaSoftware/RestPkiSamples):
Depois que o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração no [UploadController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Java/sample-spring-mvc/src/main/java/sample/controller/UploadController.java)),template [upload.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/AspNetCore/CoreWebApp/wwwroot/views/upload.html)
and API Controller [UploadController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/AspNetCore/CoreWebApp/Controllers/UploadController.cs)) é feito
o fluxo de controle é o mesmo que na amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#cades), mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta CAdES
Após o fluxo de controle da amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#cades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `cmsfile` preenchidos.
### Assinatura CAdES usando chave do servidor
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura CAdES existente
Ainda não está disponível neste projeto.
### Assinatura XML do documento inteiro
* API Controller: [XmlSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/Controllers/XmlSignatureRestController.cs)
* Componente SPA (TypeScript): [xml-signature-rest.component.ts](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/xml-signature-rest/xml-signature-rest.component.ts)
* Template SPA (HTML): [xml-signature-rest.component.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/xml-signature-rest/xml-signature-rest.component.html)
### Assinatura XML de um elemento
* API Controller: [XmlNFeSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/Controllers/XmlNFeSignatureRestController.cs)
* Componente SPA (TypeScript): [xml-nfe-signature-rest.component.ts](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/xml-nfe-signature-rest/xml-nfe-signature-rest.component.ts)
* Template SPA (HTML): [xml-nfe-signature-rest.component.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/spa/PkiSuiteAspNetSpaSample/ClientApp/src/app/components/xml-nfe-signature-rest/xml-nfe-signature-rest.component.html)
### Assinatura XAdES de um elemento
Ainda não está disponível neste projeto.
### Abrir/validar assinatura de um arquivo XML existente
Ainda não está disponível neste projeto.
### Lote de assinatura PAdES
Ainda não está disponível neste projeto.
### Lote otimizado de assinaturas PAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas CAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas XML de elementos no mesmo documento
Ainda não está disponível neste projeto.
# [Projeto de exemplos em ASP.NET Web Forms](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms)
O **Projeto de exemplos em ASP.NET Web Forms** mostra como usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto usando [ASP.NET Web Forms](https://docs.microsoft.com/en-us/aspnet/web-forms/what-is-web-forms). É hospedado no GiHub em:
https://github.com/LacunaSoftware/RestPkiSamples/tree/master/CSharp/WebForms
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/RestPkiSamples/archive/master.zip) ou clonar o [repositório](https://github.com/LacunaSoftware/RestPkiSamples.git)
1. Abra a pasta do projeto (`CSharp\WebForms`)
1. Abra o arquivo de solução (.sln) no Visual Studio
1. Execute a solução. Certifique-se de que seu sistema permita a restauração automática do pacote Nuget (se isso não ocorrer, restaure manualmente os pacotes).
## Mapa do projeto
Esta seção lista tem onde encontrar as partes relevantes em cada amostra de recurso no projeto.
### Autentificação com certificado digital
* Web Forms:
* [Authentication.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Authentication.aspx)
* [Authentication.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Authentication.aspx.cs)
* [AuthenticationSuccess.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/AuthenticationSuccess.aspx)
* [AuthenticationSuccess.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/AuthenticationSuccess.aspx.cs)
* [AuthenticationFail.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/AuthenticationFail.aspx)
* [AuthenticationFail.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/AuthenticationFail.aspx.cs)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Scripts/signature-form.js)
### Assinatura PAdES com arquivo já no servidor
* Web Forms:
* [PadesSignature.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/PadesSignature.aspx)
* [PadesSignature.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/PadesSignature.aspx.cs)
* [PadesSignatureInfo.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/PadesSignatureInfo.aspx)
* [PadesSignatureInfo.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/PadesSignatureInfo.aspx.cs)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Scripts/signature-form.js)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração em [Upload.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Upload.aspx) e e no seu *code-behind*
[Upload.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Upload.aspx.cs)) é feito o fluxo de controle é o mesmo que na amostra [PAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#pades),
mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#pades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `userfile` preenchidos.
### Marcas PAdES
Esse recurso é demonstrado como uma configuração opcional no [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#pades), amostra que por padrão começa comentada. Para ativá-lo, remova o
comentário da linha a seguir [PadesSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/PadesSignature.aspx.cs):
```cs
signatureStarter.PdfMarks.Add(PadesVisualElements.GetPdfMark(1));
```
> **Dica**
>
> Tente mudar o argumento para a função `getPdfMark()`para ver diferentes configurações de marcas PDF.
O código relevante está no arquivo [PadesVisualElements](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/PadesVisualElements.cs),
função `getPdfMark()`.
### Assinatura PAdES usando chave do servidor
Ainda não está disponível neste projeto.
### Assinatura PAdES sem a comunicação com cliente
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura PAdES existente
Ainda não está disponível neste projeto.
### Versão para impressão
Após o fluxo de controle da amostra [PAdES signature with the file already on server](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#pades) estar completo e o link *Download a printer-friendly version of the signed file* for clicado,
o fluxo vai para web form: [PrinterFriendlyVersion.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/PrinterFriendlyVersion.aspx)
e o code-behind [PrinterFriendlyVersion.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/PrinterFriendlyVersion.aspx.cs) com o arquivo de parâmetro URL `file` preenchido.
O PDF gerado contém links para web form: [Check.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Check.aspx)
e para o code-behind [Check.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Check.aspx.cs), que mostra detalhes da assinaturas.
### Assinatura CAdES com arquivo já no servidor
* Web Forms:
* [CadesSignature.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/CadesSignature.aspx)
* [CadesSignature.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/CadesSignature.aspx.cs)
* [CadesSignatureInfo.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/CadesSignatureInfo.aspx)
* [CadesSignatureInfo.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/CadesSignatureInfo.aspx.cs)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Scripts/signature-form.js)
### Assinatura CAdES com upload do arquivo pelo usuário
Depois que o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração no [Upload.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Upload.aspx))
e o code-behind [Upload.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Upload.aspx.cs é feito
o fluxo de controle é o mesmo que na amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#cades), mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta CAdES
Após o fluxo de controle da amostra [CAdES signature with file already on server](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#cades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `cmsfile` preenchidos.
### Assinatura CAdES usando chave do servidor
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura CAdES existente
Ainda não está disponível neste projeto.
### Assinatura XML do documento inteiro
Ainda não está disponível neste projeto.
### Assinatura XML de um elemento
* Web Forms:
* [XmlElementSignature.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/XmlElementSignature.aspx)
* [XmlElementSignature.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/XmlElementSignature.aspx.cs)
* [XmlElementSignatureInfo.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/XmlElementSignatureInfo.aspx)
* [XmlElementSignatureInfo.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/XmlElementSignatureInfo.aspx.cs)
* JavaScript: [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Scripts/signature-form.js)
### Assinatura XAdES de um elemento
Ainda não está disponível neste projeto.
### Abrir/validar assinatura de um arquivo XML existente
Ainda não está disponível neste projeto.
### Lote de assinatura PAdES
* Web Form: [BatchSignature.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/BatchSignature.aspx)
* [BatchSignature.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/BatchSignature.aspx.cs)
* JavaScript: [batch-signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Scripts/batch-signature-form.js)
### Lote otimizado de assinaturas PAdES
* Web Form: [BatchSignatureOptimized.aspx](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/BatchSignatureOptimized.aspx)
* [BatchSignatureOptimized.aspx.cs](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/BatchSignatureOptimized.aspx.cs)
* JavaScript: [batch-signature-optimized-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/WebForms/WebForms/Scripts/batch-signature-optimized-form.js)
### Lote de assinaturas CAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas XML de elementos no mesmo documento
Ainda não está disponível neste projeto.
# [Usando o Rest PKI em Python](https://docs.lacunasoftware.com/articles/rest-pki/python)
Rest PKI pode ser usado em Python. Para começar, veja [Projeto de exemplos em Python/Flask](https://docs.lacunasoftware.com/articles/rest-pki/python/flask).
## Biblioteca do cliente
O projeto de exemplo em Python/Flask usa o pacote [restpki-client](https://pypi.org/project/restpki-client/) do PyPI para acessar o Rest PKI em aplicações Python. Ele é compatível com Python 2 e Python 3.
Para adicioná-lo ao seu projeto, adicione a linha abaixo ao seu arquivo `requirements.txt`:
```text
restpki-client==1.3.1
```
Feito isso, execute o comando `pip install -r requirements.txt` para baixar o pacote e suas dependências.
> Se você não tiver **pip** instalado, pegue [aqui](https://pip.pypa.io/en/stable/installing/)
### Código fonte
O pacote é de código aberto, hospedado em [GitHub](https://github.com/LacunaSoftware/RestPkiPythonClient). Sinta-se à vontade para distribuir o pacote se precisar fazer personalizações.
# [Como converter do PKI Express para o REST PKI em Python](https://docs.lacunasoftware.com/articles/rest-pki/python/convert-from-express)
Este artigo explica as alterações necessárias para converter uma aplicação Python utilizando [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) para uma aplicação Python que utilize [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki).
## Equivalência de políticas
Ao migrar sua aplicação Python que utiliza o PKI Express para utilizar o REST PKI, é importante se atentar aos nomes das políticas usadas na aplicação.
Na tabela a seguir, é possível encontrar as equivalências entre os nomes das políticas em ambas bibliotecas. Na primeira coluna temos os nomes das políticas na biblioteca PKI Express, e na segunda coluna temos os nomes das mesmas políticas só que na biblioteca REST PKI.
| PKI Express | REST PKI |
|---------------------------------------------------|-----------------------------------------|
| PADES_BASIC_WITH_LTV | PADES_BASIC |
| PADES_BASIC_WITH_LTV | PADES_BASIC_WITH_PKI_BRAZIL_CERTS |
| PADES_BASIC | Política ainda não existente |
| PADES_T | PADES_T_WITH_PKI_BRAZIL_CERTS |
| PKI_BRAZIL_PADES_ADR_BASICA_WITH_LTV | PKI_BRAZIL_PADES_ADR_BASICA |
| PKI_BRAZIL_PADES_ADR_BASICA | Política ainda não existente |
| PKI_BRAZIL_PADES_ADR_TEMPO | PKI_BRAZIL_PADES_ADR_TEMPO |
| CADES_BES_WITH_REVOCATION_VALUES | CADES_BES |
| CADES_BES | CADES_BES_WITH_SIGNING_TIME_AND_NO_CRLS |
| CADES_T | Política ainda não existente |
| PKI_BRAZIL_CADES_ADR_BASICA_WITH_REVOCATION_VALUE | PKI_BRAZIL_CADES_ADR_BASICA |
| PKI_BRAZIL_CADES_ADR_BASICA | Política ainda não existente |
| PKI_BRAZIL_CADES_ADR_TEMPO | PKI_BRAZIL_CADES_ADR_TEMPO |
| PKI_BRAZIL_CADES_ADR_COMPLETA | PKI_BRAZIL_CADES_ADR_COMPLETA |
| XADES_BES | XADES_BES |
| XML_DSIG_BASIC | XML_DSIG_BASIC |
| PKI_BRAZIL_XML_ADR_BASICA | PKI_BRAZIL_XADES_ADR_BASICA |
| PKI_BRAZIL_XML_ADR_TEMPO | PKI_BRAZIL_XADES_ADR_TEMPO |
| NFE_PADRAO_NACIONAL | NFE_PADRAO_NACIONAL |
| COD_WITH_SHA1 | Política ainda não existente |
| COD_WITH_SHA256 | Política ainda não existente |
> **Observação**
>
> A política desejada ainda não existe no REST PKI? [Fale com a gente](https://www.lacunasoftware.com/pt/home/purchase).
# [Projeto de exemplos em Python com Flask](https://docs.lacunasoftware.com/articles/rest-pki/python/flask)
O **Projeto de exemplos em Python com Flask** mostra como usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) em um projeto Python usando o framework [Flask](http://flask.pocoo.org/). Está hospedado no GitHub em:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/python/flask
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou clonar o repositório
1. Instale as dependências: `pip install -r requirements.txt`
1. Veja o ambiente variável `FLASK_APP` para definir o nome do aplicativo que deve executar: `FLASK_APP=sample`
1. Execute a aplicação web: `flask run`
1. Acesse a URL [http://localhost:5000](http://localhost:5000)
## Mapa do projeto
Esta seção mostra onde encontrar partes relevantes em cada amostra de recurso do projeto.
### Autentificação com certificação digital
* View: [authentication_rest.py](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/authentication_rest.py)
* Templates:
* [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/authentication_rest/index.html) (JavaScript on [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/static/js/signature-form.js))
* [complete.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/authentication_rest/complete.html)
### Assinatura PAdES com arquivo já no servidor
* View: [pades_signature_rest.py](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_signature_rest.py)
* Templates:
* [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/pades_signature_rest/index.html) (JavaScript on [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/static/js/signature-form.js))
* [complete.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/pades_signature_rest/complete.html)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (implementado apenas para demonstração na view [server_files.py](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/server_files.py) e template [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/server_files/index.html)) o fluxo de controle é o mesmo que na amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#pades), mas com o parâmetro URL `userfile` preenchido.
### Co-assinatura PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#pades) for concluído e o link *Co-assinatura com outro certificado* for clicado, o mesmo fluxo de controle é repetido, mas agora com o parâmetro URL `userfile` preenchido.
### Marcas PAdES
Não avaliado neste projeto ainda.
### Assinatura PAdES usando chave do servidor
Não avaliado neste projeto ainda.
### Assinatura PAdES sem comunicação do cliente
Não avaliado neste projeto ainda.
### Abrir/Validar uma assinatura PAdES existente
Não avaliado neste projeto ainda.
### Versão para impressão
Não avaliado neste projeto ainda.
### Assinatura CAdES com arquivo já no servidor
* View: [cades_signature_rest.py](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/cades_signature_rest.py)
* Templates:
* [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/cades_signature_rest/index.html) (JavaScript em [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/static/js/signature-form.js))
* [complete.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/cades_signature_rest/complete.html)
### Assinatura CAdES com arquivo enviado pelo usuário
Após o upload do arquivo (implementado apenas para demonstração na view [server_files.py](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/server_files.py) e template [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/server_files/index.html)) o fluxo de controle é o mesmo que na amostra [Assinatura CAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#cades), mas com o parâmetro URL `userfile` preenchido.
### Co-assinatura CAdES
Após o fluxo de controle da amostra [Assinatura CAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#cades) for concluído e o link *Co-assinatura com outro certificado* for clicado, o mesmo fluxo de controle é repetido, mas agora com o parâmetro URL `cmsfile` preenchido.
### Assinatura CAdES usando chave do servidor
Não avaliado neste projeto ainda.
### Abrir/Validar uma assinatura CAdES existente
Não avaliado neste projeto ainda.
### Assinatura XML de um documento inteiro
* View: [xml_signature_rest.py](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/xml_signature_rest.py)
* Templates:
* [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/xml_signature_rest/index.html)
(JavaScript em [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/static/js/signature-form.js))
* [complete.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/xml_signature_rest/complete.html)
### Assinatura XML de um elemento
* View: [xml_signature_rest.py](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/xml_signature_rest.py)
* Templates:
* [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/xml_nfe_signature_rest/index.html) (JavaScript em [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/static/js/signature-form.js))
* [complete.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/xml_nfe_signature_rest/complete.html)
### Assinatura XadES de um elemento
Não avaliado neste projeto ainda.
### Lote de assinaturas PAdES
* View: [batch_pades_signature_rest.py](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/batch_pades_signature_rest.py)
* Template: [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/batch_pades_signature_rest/index.html) (JavaScript em [batch-signature-rest-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/static/js/batch-signature-rest-form.js))
### Lote otimizado de assinatura PAdES
Não avaliado neste projeto ainda.
### Lote de assinaturas CAdES
* View: [batch_cades_signature_rest.py](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/batch_cades_signature_rest.py)
* Template: [index.html](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/templates/batch_cades_signature_rest/index.html) (JavaScript em [batch-signature-rest-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/static/js/batch-signature-rest-form.js))
### Lote de assinaturas XML de elementos do mesmo documento
Não avaliado neste projeto ainda.
# [Usando o Rest PKI em Node.js](https://docs.lacunasoftware.com/articles/rest-pki/nodejs)
Rest PKI pode ser usado em Node.js. Para começar, escolha um dos projetos de amostra disponíveis:
* [Node.js MVC](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc)
* [Node.js SPA (Single Page Application)](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/spa)
## Biblioteca do cliente
Projetos de amostra All Express/Node.js usam o pacote NPM [restpki-client](https://www.npmjs.com/package/restpki-client)
para acessar aplicações do Rest Pki em Node.js.
Para adicionar isto ao seu projeto, coloque isto em seu arquivo `package.json`:
```json
{
"dependencies": {
"restpki-client": "2.3.8"
}
}
```
Após isso, faça um `npm install` para baixar o pacote e suas dependências (se você não tiver **npm** instalado, pegue [aqui](https://www.npmjs.com/get-npm))
### Source code
O pacote é de código aberto, hospedado no [GitHub](https://github.com/LacunaSoftware/RestPkiNodeClient). Sinta-se à vontade para dividir o pacote se precisar fazer alguma personalização.
# [Como converter do PKI Express para o REST PKI em NodeJS](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/convert-from-express)
Este artigo explica as alterações necessárias para converter uma aplicação NodeJS utilizando [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) para uma aplicação NodeJS que utilize [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki).
## Equivalência de políticas
Ao migrar sua aplicação NodeJS que utiliza o PKI Express para utilizar o REST PKI, é importante se atentar aos nomes das políticas usadas na aplicação.
Na tabela a seguir, é possível encontrar as equivalências entre os nomes das políticas em ambas bibliotecas. Na primeira coluna temos os nomes das políticas na biblioteca PKI Express, e na segunda coluna temos os nomes das mesmas políticas só que na biblioteca REST PKI.
| PKI Express | REST PKI |
|---------------------------------------------------|-----------------------------------------|
| PADES_BASIC_WITH_LTV | PADES_BASIC |
| PADES_BASIC_WITH_LTV | PADES_BASIC_WITH_PKI_BRAZIL_CERTS |
| PADES_BASIC | Política ainda não existente |
| PADES_T | PADES_T_WITH_PKI_BRAZIL_CERTS |
| PKI_BRAZIL_PADES_ADR_BASICA_WITH_LTV | PKI_BRAZIL_PADES_ADR_BASICA |
| PKI_BRAZIL_PADES_ADR_BASICA | Política ainda não existente |
| PKI_BRAZIL_PADES_ADR_TEMPO | PKI_BRAZIL_PADES_ADR_TEMPO |
| CADES_BES_WITH_REVOCATION_VALUES | CADES_BES |
| CADES_BES | CADES_BES_WITH_SIGNING_TIME_AND_NO_CRLS |
| CADES_T | Política ainda não existente |
| PKI_BRAZIL_CADES_ADR_BASICA_WITH_REVOCATION_VALUE | PKI_BRAZIL_CADES_ADR_BASICA |
| PKI_BRAZIL_CADES_ADR_BASICA | Política ainda não existente |
| PKI_BRAZIL_CADES_ADR_TEMPO | PKI_BRAZIL_CADES_ADR_TEMPO |
| PKI_BRAZIL_CADES_ADR_COMPLETA | PKI_BRAZIL_CADES_ADR_COMPLETA |
| XADES_BES | XADES_BES |
| XML_DSIG_BASIC | XML_DSIG_BASIC |
| PKI_BRAZIL_XML_ADR_BASICA | PKI_BRAZIL_XADES_ADR_BASICA |
| PKI_BRAZIL_XML_ADR_TEMPO | PKI_BRAZIL_XADES_ADR_TEMPO |
| NFE_PADRAO_NACIONAL | PKI_BRAZIL_NFE_PADRAO_NACIONAL |
| COD_WITH_SHA1 | Política ainda não existente |
| COD_WITH_SHA256 | Política ainda não existente |
> **Observação**
>
> A política desejada ainda não existe no REST PKI? [Fale com a gente](https://www.lacunasoftware.com/pt/home/purchase).
# [Projeto de exemplos em Node.js MVC](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc)
O **Projeto de exemplos em Node.js MVC** mostra como usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em projeto Node.js seguindo o padrão Model-View-Controller. É hospedado no GitHub em:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/nodejs/expressmvc
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou clonar o [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Instale as dependências: `npm install`
1. Execute a aplicação: `npm start`
1. Acesse o URL [http://localhost:3000](http://localhost:3000)
## Mapa do projeto
Esta seção lista tem onde encontrar as partes relevantes em cada amostra de recurso no projeto.
### Autentificação com certificado digital
* Route: [authentication-restpki.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/authentication-restpki.js)
* Views:
* [authentication-rest/index.pug](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/views/authentication-rest/index.pug)
(JavaScript on [authentication-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/public/javascripts/authentication-form.js))
* [authentication-rest/failed.pug](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/views/authentication-rest/failed.pug)
### Assinatura PAdES com arquivo já no servidor
* Route: [pades-signature.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/pades-signature.js)
* Views:
* [pades-signature.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/pades-signature.pug)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/public/javascripts/signature-form.js))
* [pades-signature-complete.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/pades-signature-complete.pug)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração em [upload.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/upload.js) e
[upload.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/upload.pug)) é feito o fluxo de controle é o mesmo que na amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#pades),
mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#pades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `userfile` preenchidos.
### Marcas PAdES
Ainda não está disponível neste projeto.
### Assinatura PAdES usando chave do servidor
* Route: [pades-signature-server-key.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/pades-signature-server-key.js)
* View: [pades-signature-complete.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/pades-signature-complete.pug)
### Assinatura PAdES sem a comunicação com cliente
Ainda não está disponível neste projeto.
### Abrir/validar uma assinatura PAdES existente
* Route: [open-pades-signature.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/open-pades-signature.js)
* View: [open-pades-signature.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/open-pades-signature.pug)
### Versão para impressão
Após o fluxo do controle da amostra [Assinatura PAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#pades) estiver completo e o link *Download a printer-friendly version of the signed file* for clicado, o fluxo ,
vai para web form: [printer-friendly-version.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/printer-friendly-version.js) com o arquivo de parâmetro URL `file` preenchido.
### Assinatura CAdES com arquivo já no servidor
* Route: [cades-signature.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/cades-signature.js)
* Views:
* [cades-signature.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/cades-signature.pug)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/public/javascripts/signature-form.js))
* [cades-signature-complete.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/cades-signature-complete.pug)
### Assinatura CAdES com upload do arquivo pelo usuário
Depois que o upload do arquivo (que é grosseiramente implementado apenas para fins de demonstração no [upload.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/upload.js) e [upload.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/upload.pug)) é feito
o fluxo de controle é o mesmo que na amostra [Assinatura CAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#cades), mas com parâmetro de URL `userfile` preenchidos.
### Assinatura conjunta CAdES
Após o fluxo de controle da amostra [Assinatura CAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#cades) está concluído e o link *Co-sign with another certificate* é clicado, o mesmo fluxo de controle é
repetido, mas agora com parâmetro de URL `cmsfile` preenchidos.
### Assinatura CAdES usando chave do servidor
* Route: [cades-signature-server-key.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/cades-signature-server-key.js)
* View: [cades-signature-complete.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/cades-signature-complete.pug)
### Abrir/validar uma assinatura CAdES existente
* Route: [open-cades-signature.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/open-cades-signature.js)
* View: [open-cades-signature.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/open-cades-signature.pug)
### Assinatura XML do documento inteiro
* Route: [xml-full-signature.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/xml-full-signature.js)
* Views:
* [xml-full-signature.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/xml-full-signature.pug)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/public/javascripts/signature-form.js))
* [xml-signature-complete.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/xml-signature-complete.pug)
### Assinatura XML de um elemento
* Route: [xml-element-signature.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/routes/xml-element-signature.js)
* Views:
* [xml-element-signature.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/xml-element-signature.pug)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/public/javascripts/signature-form.js))
* [xml-signature-complete.pug](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/mvc/views/xml-signature-complete.pug)
### Assinatura XAdES de um elemento
Ainda não está disponível neste projeto.
### Abrir/validar assinatura de um arquivo XML existente
Ainda não está disponível neste projeto.
### Lote de assinatura PAdES
Ainda não está disponível neste projeto.
### Lote otimizado de assinaturas PAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas CAdES
Ainda não está disponível neste projeto.
### Lote de assinaturas XML de elementos no mesmo documento
Ainda não está disponível neste projeto.
**Demonstrações e exemplos**
- Sign a PDF with Rest PKI without browser access to Rest PKI: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-wo-communication-restpki.js)
# [Projeto de exemplos em Node.js SPA](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/spa)
O **Projeto de exemplos em Node.js SPA** mostra como usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto Node.js que segue o padrão Single Page Application (SPA), com [AngularJS](https://angularjs.org/) no frontend.
Ele está hospedado no GitHub em:
https://github.com/LacunaSoftware/RestPkiSamples/tree/master/NodeJS/angular-spa
## Executando o projeto
1. [Baixe o projeto](https://github.com/LacunaSoftware/RestPkiSamples/archive/master.zip) ou clone o [repositório](https://github.com/LacunaSoftware/RestPkiSamples.git)
1. Gere um token de acesso à API no [site do REST PKI](https://pki.rest/)
1. Cole o seu token de acesso no arquivo [restpki-client.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/angular-spa/restpki-client.js)
1. Instale as dependências: `npm install`
1. Execute a aplicação: `npm start`
1. Acesse o URL [http://localhost:3000](http://localhost:3000)
## Mapa do projeto
Esta seção indica onde encontrar as partes relevantes de cada exemplo de funcionalidade deste projeto.
### Autenticação com certificado digital
* API: [app.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/angular-spa/app.js)
* Controller da SPA: [authentication.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/angular-spa/webapp/js/controllers/authentication.js)
* Template da SPA: [authentication.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/CSharp/AspNetCore/CoreWebApp/wwwroot/views/authentication.html)
### Assinatura PAdES com arquivo já no servidor
* API: [app.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/angular-spa/app.js)
* Controller da SPA: [pades-signature.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/angular-spa/webapp/js/controllers/pades-signature.js)
* Template da SPA: [pades-signature.html](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/NodeJS/angular-spa/webapp/views/pades-signature.html)
### Assinatura PAdES com arquivo enviado pelo usuário
Ainda não disponível neste projeto.
### Assinatura conjunta PAdES
Ainda não disponível neste projeto.
### Marcas PAdES
Ainda não disponível neste projeto.
### Assinatura PAdES usando chave do servidor
Ainda não disponível neste projeto.
### Assinatura PAdES sem comunicação com o cliente
Ainda não disponível neste projeto.
### Abrir/validar uma assinatura PAdES existente
Ainda não disponível neste projeto.
### Versão para impressão
Ainda não disponível neste projeto.
### Assinatura CAdES com arquivo já no servidor
Ainda não disponível neste projeto.
### Assinatura CAdES com arquivo enviado pelo usuário
Ainda não disponível neste projeto.
### Assinatura conjunta CAdES
Ainda não disponível neste projeto.
### Assinatura CAdES usando chave do servidor
Ainda não disponível neste projeto.
### Abrir/validar uma assinatura CAdES existente
Ainda não disponível neste projeto.
### Assinatura XML do documento inteiro
Ainda não disponível neste projeto.
### Assinatura XML de um elemento
Ainda não disponível neste projeto.
### Assinatura XAdES de um elemento
Ainda não disponível neste projeto.
### Abrir/validar assinaturas de um arquivo XML existente
Ainda não disponível neste projeto.
### Lote de assinaturas PAdES
Ainda não disponível neste projeto.
### Lote otimizado de assinaturas PAdES
Ainda não disponível neste projeto.
### Lote de assinaturas CAdES
Ainda não disponível neste projeto.
### Lote de assinaturas XML de elementos do mesmo documento
Ainda não disponível neste projeto.
# [Usando o Rest PKI em Ruby](https://docs.lacunasoftware.com/articles/rest-pki/ruby)
O Rest PKI pode ser usado em Ruby. Para começar, veja [Projeto de Exemplos em Ruby on Rails](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails).
## Biblioteca do cliente
Para usar o Rest PKI em aplicações Ruby, use nosso **Ruby Gem** [rest_pki](https://rubygems.org/gems/rest_pki) declarando em seu `Gemfile`:
```gemspec
gem 'rest_pki', '~> 1.1.0'
```
Após isos, faça um `bundle install` para download do gem e suas dependências (se você não tiver o Bundler instalado, pegue [aqui](http://bundler.io/))
Como alternativa, você pode instalar a gema diretamente na linha de comando:
```sh
gem install rest_pki
```
O gem é código aberto, hospedado no [GitHub](https://github.com/LacunaSoftware/RestPkiRubyClient). Sinta-se à vontade para fazer o *fork* do repositório se precisar de alguma personalização.
# [Como converter do PKI Express para o REST PKI em Ruby](https://docs.lacunasoftware.com/articles/rest-pki/ruby/convert-from-express)
Este artigo explica as alterações necessárias para converter uma aplicação Ruby utilizando [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) para uma aplicação Ruby que utilize [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki).
## Equivalência de políticas
Ao migrar sua aplicação Ruby que utiliza o PKI Express para utilizar o REST PKI, é importante se atentar aos nomes das políticas usadas na aplicação.
Na tabela a seguir, é possível encontrar as equivalências entre os nomes das políticas em ambas bibliotecas. Na primeira coluna temos os nomes das políticas na biblioteca PKI Express, e na segunda coluna temos os nomes das mesmas políticas só que na biblioteca REST PKI.
| PKI Express | REST PKI |
|---------------------------------------------------|-------------------------------|
| PADES_BASIC_WITH_LTV | PADES_BASIC |
| PADES_BASIC_WITH_LTV | PADES_BASIC_WITH_ICPBR_CERTS |
| PADES_BASIC | Política ainda não existente |
| PADES_T | PADES_T_WITH_ICPBR_CERTS |
| PKI_BRAZIL_PADES_ADR_BASICA_WITH_LTV | Política ainda não existente |
| PKI_BRAZIL_PADES_ADR_BASICA | Política ainda não existente |
| PKI_BRAZIL_PADES_ADR_TEMPO | Política ainda não existente |
| CADES_BES_WITH_REVOCATION_VALUES | CADES_BES |
| CADES_BES | Política ainda não existente |
| CADES_T | Política ainda não existente |
| PKI_BRAZIL_CADES_ADR_BASICA_WITH_REVOCATION_VALUE | CADES_ICPBR_ADR_BASICA |
| PKI_BRAZIL_CADES_ADR_BASICA | Política ainda não existente |
| PKI_BRAZIL_CADES_ADR_TEMPO | CADES_ICPBR_ADR_TEMPO |
| PKI_BRAZIL_CADES_ADR_COMPLETA | CADES_ICPBR_ADR_COMPLETA |
| XADES_BES | XML_XADES_BES |
| XML_DSIG_BASIC | XML_DSIG_BASIC |
| PKI_BRAZIL_XML_ADR_BASIC | XML_ICPBR_ADR_BASICA |
| PKI_BRAZIL_XML_ADR_TEMPO | XML_ICPBR_ADR_TEMPO |
| NFE_PADRAO_NACIONAL | XML_ICPBR_NFE_PADRAO_NACIONAL |
| COD_WITH_SHA1 | Política ainda não existente |
| COD_WITH_SHA256 | Política ainda não existente |
> **Observação**
>
> A política desejada ainda não existe no REST PKI? [Fale com a gente](https://www.lacunasoftware.com/pt/home/purchase).
# [Projeto de exemplos em Ruby on Rails](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails)
O **Projeto de exemplos em Ruby on Rails** mostra como usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) junto com [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) em um projeto de Ruby usando o framework [Ruby on Rails](http://rubyonrails.org/). Isto está hospedado no GitHub em:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/ruby/rails
## Executando o projeto
1. [Download do projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou clone o [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Instale as dependências: `bundle install`
1. Execute a aplicação: `rails server`
1. Accesse a URL [http://localhost:3000](http://localhost:3000)
## Mapa do projeto
Esta seção lista onde você vai encontrar partes relevantes nas amostras do projeto.
### Autentificação com certificado digital
* Controller: [AuthenticationRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/authentication_rest_controller.rb)
* Views:
* [index.html.erb](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/views/authentication_rest/index.html.erb)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/assets/javascripts/signature-form.js))
* [action.html.erb](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/views/authentication_rest/action.html.erb)
### Assinatura PAdES com arquivo já no servidor
* Controller: [PadesSignatureRestController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_signature_rest_controller.rb)
* Views:
* [index.html.erb](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/views/pades_signature_rest/index.html.erb)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/assets/javascripts/signature-form.js))
* [action.html.erb](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/views/pades_signature_rest/action.html.erb)
### Assinatura PAdES com arquivo enviado pelo usuário
Após o upload do arquivo (apenas para demonstração no controlador [ServerFilesController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/server_files_controller.rb) e
visualização [index.html.erb](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/views/server_files/index.html.erb)) o fluxo de controle é o mesmo que na amostra [Assinatura PAdES com arquivo existente já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#pades), mas com o parâmetro URL `userfile` preenchido.
### Co-assinatura PAdES
Após o fluxo de controle da amostra [Assinatura PAdES com arquivo já existente](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#pades) for concluído e o link *Co-assinar com outro certificado* for clicado, o
mesmo fluxo de controle é repetido, mas agora com o parâmetro de URL `userfile` preenchido.
### Marcas PAdES
Não avaliado para este projeto ainda.
### Assinatura PAdES usando chave do servidor
Não avaliado para este projeto ainda.
### Assinatura PAdES sem comunicação do cliente
* Controller: [PadesSignatureExpressController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_signature_express_controller.rb)
* Views:
* [index.html.erb](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/views/pades_signature_express/index.html.erb)
(JavaScript on [signature-start-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/assets/javascripts/signature-start-form.js))
### Abrir/validar uma assinatura PAdES existente
Não avaliado para este projeto ainda.
### Versão para impressão
Não avaliado para este projeto ainda.
### Assinatura CAdES com arquivo já no servidor
* Controller: [CadesSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/controllers/cades_signature_controller.rb)
* Views:
* [index.html.erb](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/views/cades_signature/index.html.erb)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/assets/javascripts/signature-form.js))
* [action.html.erb](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/views/cades_signature/action.html.erb)
### Assinatura CAdES com arquivo enviado pelo usuário
Após o upload do arquivo (implementado de forma rudimentar, apenas para fins de demonstração, no controller
[ServerFilesController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/server_files_controller.rb)
e na view
[index.html.erb](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/views/server_files/index.html.erb)),
o fluxo de controle é o mesmo da amostra [Assinatura CAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#cades), mas com o parâmetro de URL `userfile` preenchido.
### Assinatura conjunta CAdES
Após o fluxo de controle da amostra [Assinatura CAdES com arquivo já no servidor](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#cades) ser concluído e o link *Co-sign with another certificate* for clicado, o
mesmo fluxo de controle é repetido, mas agora com o parâmetro de URL `userfile` preenchido.
### Assinatura CAdES usando chave do servidor
Não avaliado para este projeto ainda.
### Abrir/validar uma assinatura CAdES existente
Não avaliado para este projeto ainda.
### Assinatura XML de um documento inteiro
* Controller: [FullXmlSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/controllers/full_xml_signature_controller.rb)
* [index.html.erb](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/views/full_xml_signature/index.html.erb)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/assets/javascripts/signature-form.js))
* [action.html.erb](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/views/full_xml_signature/action.html.erb)
### Assinatura XML de um elemento
* Controller: [XmlElementSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/controllers/xml_element_signature_controller.rb)
* Views:
* [index.html.erb](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/views/xml_element_signature/index.html.erb)
(JavaScript on [signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/assets/javascripts/signature-form.js))
* [action.html.erb](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/views/xml_element_signature/action.html.erb)
### Assinatura XAdES de um elemento
Não avaliado para este projeto ainda.
### Abrir/validar assinaturas em um arquivo XML existente
Não avaliado para este projeto ainda.
### Lote de assinaturas PAdES
* Controller: [BatchSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/controllers/batch_signature_controller.rb)
* View: [index.html.erb](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/views/batch_signature/index.html.erb)
(JavaScript on [batch-signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/assets/javascripts/batch-signature-form.js))
### Lote otimizado de assinaturas PAdES
Não avaliado para este projeto ainda.
### Lote de assinaturas CAdES
* Controller: [CadesBatchSignatureController](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/controllers/cades_batch_signature_controller.rb)
* View: [index.html.erb](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/views/cades_batch_signature/index.html.erb)
(JavaScript on [batch-signature-form.js](https://github.com/LacunaSoftware/RestPkiSamples/blob/master/Ruby/app/assets/javascripts/batch-signature-form.js))
### Lote de assinaturas XML de elementos do mesmo documento
Não avaliado para este projeto ainda.
# [Rest PKI *on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises)
Se por algum motivo você não possa ou não queira utilizar um serviço em nuvem, é possível hospedar uma
instância do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) na sua própria infraestrutura ("on premises"). Veja o
[material comercial do produto](https://www.lacunasoftware.com/pt/home/certificate#/rest) para mais informações.
## Configuração do WebPKI
Caso a sua aplicação que utilize o WebPKI é necessário configurar a URL do RestPKI e a licença do WebPKI.
* [Configuração do WebPKI para utilização do RestPKI on premises](https://docs.lacunasoftware.com/articles/web-pki/customizing-restpki-url)
## Ambientes suportados
O Rest PKI é compatível com os seguintes ambientes:
* [Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup)
* [Azure App Service](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/azure-setup)
> **Observação**
>
> Para ambientes Linux, recomendamos o uso do [PKI Express](https://docs.lacunasoftware.com/articles/pki-express)
Clique em um dos ambientes acima para obter instruções específicas de instalação e atualização.
## Veja também
* [Dimensionamento de servidores](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/sizing)
* [Configuração de Carimbo de Tempo](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/configure-timestamping)
# [Recuperação de senha de admin - Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/admin-recover)
Caso tenha esquecido a senha do usuário *Admin*, criado durante o processo de instalação da sua instância
[*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki), execute no banco de dados o comando SQL abaixo
para resetar a senha para `Senha!01`:
```sql
update "dbo"."AspNetUsers"
set "PasswordHash"='AClrojBpn4Y/ly8qXXVkvVN67/oUsCxaMdKD7Bq0GWsLbXXVqz5a4V6g06JqIemJ3A=='
where "UserName"='Admin';
```
Uma vez resetada a senha, autentique-se no Rest PKI (usuário `Admin` senha `Senha!01`) e, no
menu superior, clique no nome do usuário (**Admin**) > **Conta** e clique no botão **Alterar senha**.
# [Rest PKI - Instalação e atualização em Azure App Service](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/azure-setup)
É possível instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) no [Azure App Service](https://azure.microsoft.com/pt-br/services/app-service/),
serviço em nuvem da Microsoft que permite hospedagem de aplicações sem que seja necessário administrar a máquina virtual.
Para começar a instalação, você precisará:
* Pacote de binários do Rest PKI: [restpki-1.33.9-azure.zip](https://cdn.lacunasoftware.com/restpki/restpki-1.33.9-azure.zip)
* Licença binária de uso do Lacuna PKI SDK (arquivo LacunaPkiLicense.txt)
De posse desses dois arquivos, [entre em contato conosco](https://www.lacunasoftware.com/pt/home/purchase) para obter as instruções de instalação.
# [Alterando a licença de uso - Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/change-license)
Para utilizar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki), é necessária uma licença
de uso do Lacuna PKI SDK, a qual é solicitada durante o processo de instalação.
Caso seja necessário futuramente alterar a licença sendo utilizada, siga os passos abaixo:
1. Autentique-se como administrador na sua instância do Rest PKI (caso não saiba a senha, veja o artigo [Recuperação de senha de admin](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/admin-recover))
1. No menu superior, clique em **Administração** > **Status do sistema**
1. No item **SDK license loaded**, clique no botão **Alterar**
1. Localize no arquivo **LacunaPkiLicense.txt** a licença binária do PKI SDK, na seção "Binary license content (Base64-encoded)"
```text
Binary license content (Base64-encoded)
---------------------------------------
AxAA........................................iw==
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
1. Copie e cole **a linha inteira**, em seguida clique no botão **Configurar licença**

# [Verificando a versão instalada do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/check-version)
Para verificar a versão que está instalada da sua instância [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki),
acesse o seguinte endereço a partir de um navegador:
```text
https://endereco-do-seu-restpki/api/system/info
```
Exemplo: https://pki.rest/api/system/info
O retorno é um XML contendo a versão do sistema, por exemplo:
```xml
Lacuna Rest PKI
1.17.1
```
Para atualizar a sua instância do Rest PKI, veja o artigo [Atualização do Rest PKI em Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup/update)
# [Configuração de Proxy - Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/configure-proxy)
É possível configurar a sua instância [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) para utilizar um proxy para saída
para a internet. Para isso, preencha as seguintes configurações no arquivo *AppSettings.config*:
* **ProxyAddress**: endereço do proxy, ex: `http://10.1.1.111:8888/`
* **ProxyUsername** e **ProxyPassword**: caso o proxy necessite de usuário e senha, preencha aqui esses dados
* **ProxyUseDefaultCredentials**: atribua o valor `True` caso o seu proxy utilize autenticação integrada com o Active Directory (incomum)
* **ProxyBypassOnlocal**: atribua o valor `True` para não utilizar o proxy para endereços locais
> **Observação**
>
> É importante que o Rest PKI tenha saída irrestrita para a internet pois é necessário obter as Listas de Certificados Revogados
> (LCRs) mais recentes emitidas pelas Autoridades Certificadoras para validar o estado de revogação de certificados digitais.
# [Configuração de Storage - Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/configure-storage)
Para manter o banco de dados organizado, o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) armazena arquivos temporários fora do banco de dados.
Os arquivos podem ser armazenados em *serviços* diferentes:
* Local (sistema de arquivos)
* [Azure Storage](https://azure.microsoft.com/en-us/services/storage/)
* [Amazon S3](https://aws.amazon.com/s3)
Por padrão, as instâncias [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) vêm configuradas para armazenar arquivos na pasta temporária
do servidor web. Entretanto, pode ser necessário alterar essa configuração, especialmente caso você queira executar
o Rest PKI em múltiplos servidores. Nesse caso, é necessário que as várias instâncias compartilhem o mesmo banco de
dados **e o mesmo storage**.
A configuração de storage é feita pela interface web do sistema:
1. Autentique-se com um usuário administrador
1. Vá em **Administração** > **Status do Sistema**
Em seguida, configure a seção **Serviço de Armazenamento** conforme uma das seções abaixo.

## Local
Para armazenar os arquivos temporários no sistema de arquivos local, na opção **Serviço Base** escolha **Local**.
Em seguida, preencha a opção **Diretório do Armazenamento Local** com o caminho da pasta onde o Rest PKI deve armazenar os arquivos. Exemplo:
```text
C:\AppData\RestPki
```
> **Observação**
>
> Em uma instalação multi-servidores, essa pasta deve ser compartilhada entre os servidores web
## Azure Storage
Para armazenar os arquivos temporários em um *container* do [Azure Storage](https://azure.microsoft.com/en-us/services/storage/),
na opção **Serviço Base** escolha **Azure Blob Storage**.
Em seguida, preencha a opção **Configuração do Azure** da seguinte maneira:
```text
;;
```
Exemplo:
```text
patorumstorage;MYSECRETKEY;restpkifiles
```
## Amazon S3
Para armazenar os arquivos temporários em um *bucket* do [Amazon S3](https://aws.amazon.com/s3),
na opção **Serviço Base** escolha **Amazon S3**.
Em seguida, preencha a opção **Configuração do Amazon S3** da seguinte maneira:
```text
;;
```
Exemplo:
```text
MYACCESSKEY;MYSECRETKEY;restpkifiles
```
# [Configuração de Carimbo de Tempo - Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/configure-timestamping)
Para realizar assinaturas digitais na sua instância [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) utilizando
políticas de assinatura que requeiram carimbo de tempo de assinatura, por exemplo políticas **PAdES-T** ou
**CAdES Ad-RT**, é necessário contratar um serviço externo de provimento de carimbos de tempo.
> **Observação**
>
> Você pode utilizar o Rest PKI em nuvem (https://pki.rest/) para prover carimbos de tempo para a sua instância do Rest PKI.
> Veja os preços [aqui](https://www.lacunasoftware.com/pt-br/pricing/#cloud-simulator).
O serviço de provimento de carimbos de tempo deve atender aos seguintes requisitos:
* Comunicação via *Timestamp Protocol* (TSP - RFC 3161) sobre HTTP ou HTTPS
* Formas de autenticação suportadas:
* HTTP basic authentication
* SSL/TLS com autenticação mútua
* OAuth Bearer Token
Caso opte por utilizar o Rest PKI cloud, siga os passos abaixo para obter as credenciais de acesso:
1. Autentique-se no site do Rest PKI cloud (https://pki.rest/)
1. No Painel de Controle, clique no botão **Gerar novo token de acesso**
1. Copie o valor gerado
Caso opte por outro provedor de carimbos de tempo, solicite os dados de acesso ao provedor (URL e credenciais).
Uma vez tendo os dados de acesso ao provedor de carimbos de tempo, siga os passos abaixo para configurar a sua
instância do Rest PKI:
1. Acesse a sua instância do Rest PKI
1. Autentique-se com usuário "Admin" ou outro usuário administrador (a senha foi definida durante a instalação)
1. No menu superior, vá em **Administração** > **Tipos de Carimbo de Tempo**
1. Clique no item *ICP-Brasil*

1. Na área **Carimbadoras de Tempo**, digite um nome e clique em **Criar**

1. Na tela seguinte, preencha a URL do provedor e clique em **Create**
* No caso do Rest PKI cloud, a URL é: `https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310`
1. Uma vez criada a carimbadora, entre nas configurações dela

1. No campo **Autenticação**, escolha a opção especificada pelo provedor
* No caso do Rest PKI cloud, a opção deve ser **OAuth Bearer Token**
1. Dependendo da escolha feita, um novo campo será exibido e deve ser preenchido:
1. *HTTP basic authentication*: preencha o campo **Credenciais** com o usuário e senha fornecidos pelo provedor
1. *OAuth Berer Token*: preencha o campo **Token OAuth** com o valor do token fornecido pelo provedor (**sem** o prefixo "Bearer")
1. SSL com autenticação mútua: veja a [seção abaixo](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/configure-timestamping#ssl-mutual-auth)
1. Clique no botão **Testar**. Caso apareça a mensagem "O teste foi bem sucedido", a configuração está OK.
## Autenticação via SSL/TLS com autenticação mútua
Para utilizar um provedor de carimbos de tempo que exija autenticação via SSL/TLS com autenticação mútua, você precisa
primeiro importar o certificado fornecido pelo provedor no repositório de certificados do Windows, na pasta Personal do
usuário que é utilizado para executar o Rest PKI.
De posse do arquivo .pfx (PKCS #12) fornecido pelo provedor, siga uma das seções abaixo dependendo do ambiente no qual o
Rest PKI está instalado para importar o certificado para o repositório do Windows.
### Configuração em Windows Server
1. Acesse o servidor onde o Rest PKI está instalado
1. Crie um usuário local para o Rest PKI
1. No menu iniciar, busque por "Edit local users and groups" e clique nele
1. Clique com o botão direito na pasta Users e clique em **New User...**
1. Preencha um nome do usuário e a senha
1. Desmarque a opção *User must change password at next logon*
1. Marque a opção *Password never expires*
1. Clique em **Create**
1. Conceda as permissões necessárias ao novo usuário na pasta de instalação do Rest PKI
1. Localize a pasta na qual o Rest PKI está instalado (ex: `c:\inetpub\RestPKI`)
1. Clique com o botão direito na pasta e vá na aba **Security**
1. Clique em **Edit...** > **Add..** > digite o nome do usuário criado e clique em **OK**
1. Deixe marcados apenas os itens **List folder contents** e **Read**
1. Clique em **OK**
1. Altere a identidade do *application pool*
1. Abra o *IIS Manager*
1. Vá nos *Application Pools*
1. Clique no *application pool* do Rest PKI
1. Clique em **Advanced Settings...** no menu da direita
1. Na seção **Process Model**, edite a configuração **Identity**
1. Escolha a opção **Custom account**
1. Clique no botão **Set...** e entre com as credenciais escolhidas para o usuário criado
1. Clique em **OK**
1. Verifique se o Rest PKI continua funcionando normalmente (acesse a interface web)
1. Navegue até a pasta `C:\Windows\System32`
1. Localize o arquivo `mmc.exe`
1. Pressione a tecla SHIFT e clique com o botão direito no arquivo
1. Clique na opção **Run as different user**

1. Entre com as credenciais do usuário criado
1. Vá em **File** > **Add/Remove Snap-in...**
1. Clique em **Certificates**, clique no botão **Add >** e clique em **OK**
1. Expanda a pasta **Certificates - Current User**
1. Clique com o botão direito na pasta **Personal** > **All Tasks** > **Import...**
1. Siga o *wizard* fornecendo o arquivo .pfx e a senha deste
Uma vez importado o certificado, ele será listado no campo **SSL Certificate** da configuração do provedor de carimbos de tempo do Rest PKI.
### Configuração em Azure App Service
1. Acesse as configurações do App Service no dashboard do Azure
1. Acesse os **SSL certificates** do App Service
1. Clique em **Upload Certificate**
1. Forneça o arquivo .pfx e a senha deste e clique em **Upload**
1. Após o término da operação, o certificado aparecerá na lista de certificados
1. Copie o valor da coluna **Thumbprint** do certificado
1. Acesse os **Application settings** do App Service
1. Na seção *Application settings*, adicione uma nova configuração
* Nome: `WEBSITE_LOAD_CERTIFICATES`
* Valor: (valor copiado da coluna *Thumbprint*)
1. Salve as configurações
Uma vez importado o certificado, ele será listado no campo **SSL Certificate** da configuração do provedor de carimbos de tempo do Rest PKI.
# [Atualização do banco de dados - Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/database-update)
As atualizações do banco de dados em instâncias [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) podem
ser feitas **automaticamente** ou **manualmente**.
No modo automático, o banco de dados é atualizado na primeira execução de uma nova versão do sistema, não sendo
necessário qualquer procedimento adicional. Já no caso manual, após uma atualização de versão do sistema que
requer atualização do banco de dados, o sistema fica inoperante até que o procedimento seja realizado manualmente
(veja [seção abaixo](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/database-update#manual-update)).
A configuração mais adequada depende sobretudo da escolha feita durante a instalação sistema no que se refere às
permissões do sistema sobre o banco de dados:
1. Sistema ser "dono" (*owner*) do banco de dados **ou**
1. Sistema ter apenas permissões de leitura e escrita no banco de dados
Caso se tenha optado pela opção **1** (sistema ***owner* do banco**), ambas configurações de atualização do banco
podem ser utilizadas, sendo recomendada a configuração de atualizações **automáticas**. Caso se tenha optado pela
opção **2** (sistema com permissões de **leitura e escrita apenas** no banco de dados), o sistema deve ser
obrigatoriamente configurado para efetuar atualizações no banco de dados **manualmente**.
## Configuração do modo de atualização
A configuração do modo de atualização -- manual ou automática -- é feita no arquivo `AppSettings.config`, no item `AutoUpdateDatabase`.
Para utilizar atualizações **automáticas** do banco de dados (configuração recomendada para quanto o sistema é ***owner* do banco**):
```xml
```
Para utilizar atualizações **manuais** do banco de dados (configuração obrigatória para quando o sistema tem permissões de **leitura e escrita apenas** sobre o banco de dados):
```xml
```
> **Observação**
>
> Caso a configuração seja omitida, assume-se atualização **manual** (que não é o ideal quando o sistema é *owner* do banco de dados).
## Atualização manual do banco de dados
Caso ao atualizar o Rest PKI apareça a mensagem "HTTP Error 503.0 - The server is currently undergoing maintenance. Please try again later.", isso significa que é preciso atualizar o banco de dados manualmente.
> **Observação**
>
> Para saber com antecedência se uma atualização demandará atualização do banco de dados, observe no [Histórico de versões](https://docs.lacunasoftware.com/articles/rest-pki/changelog) se alguma das
> versões entre a versão que encontra-se instalada e a que você está instalando está marcada como demandando atualização do banco de dados. Para saber como
> verificar a versão instalada do sistema, veja [este artigo](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/check-version).
Nesse caso, acesse a URL `/Maintenance` relativa ao endereço do site (por exemplo, `http://localhost:9000/Maintenance`) e siga as instruções na tela.
> **Observação**
>
> Para acessar essa URL é preciso estar acessando o site localmente ou via HTTPS. Caso precise acessar essa URL remotamente via HTTP, veja a seção 2.4.5 do [Manual de instalação em Windows Server](https://cdn.lacunasoftware.com/restpki/restpki-admin-guide-pt.pdf)

Nesse passo, o sistema irá atualizar as tabelas no banco de dados. Utilize uma das opções exibidas na tela para efetuar as alterações, de acordo com a opção de permissões da aplicação sobre o banco de dados escolhida durante a instalação:
* Caso o sistema seja *owner* do banco de dados, prossiga com a opção **A** (*Perform changes with the application’s user*)
* Caso o sistema tenha apenas permissões de leitura e escrita no banco de dados, prossiga com a opção **B** (*Provide
credentials for a privileged user*). Forneça então "RestPkiAdmin" como usuário privilegiado (ou o nome de usuário escolhido
durante a instalação como usuário administrador do banco de dados) e a senha deste
Após seguir as instruções, a tela inicial com a mensagem "REST PKI on premises" será exibida e o procedimento de atualização estará concluído.
## Veja também
* [Resolução de problemas de instalação em Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup/troubleshoot)
# [Dimensionamento de servidores - Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/sizing)
Caso você precise de ajuda para dimensionar os seus servidores web para executar uma [instância *on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki),
utilize o documento abaixo contendo uma avaliação da performance do Rest PKI:
[Rest PKI - Teste de Performance v1.0 - 31/08/2017](https://cdn.lacunasoftware.com/restpki/restpki-perf-test-pt.pdf)
# [Rest PKI - Instalação e atualização em Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup)
Os requisitos para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) em Windows Server são:
* Sistema operacional: Windows Server 2008 R2 ou superior
* Banco de dados:
* SQL Server 2008 R2 ou superior **ou**
* PostgreSQL 9.3 ou superior
Para começar a instalação, você precisará:
* Pacote de binários do Rest PKI: [restpki-1.33.9.zip](https://cdn.lacunasoftware.com/restpki/restpki-1.33.9.zip)
* Licença binária de uso do Lacuna PKI SDK (arquivo LacunaPkiLicense.txt)
> **Observação**
>
> Caso ainda não tenha uma licença de uso, [solicite uma licença de teste](https://www.lacunasoftware.com/pt/home/purchase).
De posse desses dois arquivos, prossiga a algum dos tópicos abaixo:
1. [Instalação](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup/install)
1. [Atualização](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup/update)
## Veja também
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup/troubleshoot)
# [Instalação do Rest PKI em Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup/install)
Este artigo descreve como instalar uma [instância própria](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) em um Windows Server.
> **Observação**
>
> Antes de começar, veja os [pré-requisitos para instalar o Rest PKI em Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup)
Esta documentação está em processo de migração. Por ora, **veja o capítulo 2 do [Manual de instalação em Windows Server](https://cdn.lacunasoftware.com/restpki/restpki-admin-guide-pt.pdf)**
# [Resolução de problemas de instalação](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup/troubleshoot)
Aqui estão reunidos artigos que podem auxiliar na resolução de problemas na instalação e atualização de uma [instância própria](https://docs.lacunasoftware.com/articles/rest-pki/on-premises) do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) em Windows Server.
Esta documentação está em processo de migração. Por ora, **veja o capítulo 4 do [Manual de instalação em Windows Server](https://cdn.lacunasoftware.com/restpki/restpki-admin-guide-pt.pdf)**
# [Atualização do Rest PKI em Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup/update)
Para atualizar a sua instância do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki), siga as instruções dessa página.
> **Dica**
>
> Antes de começar, verifique a [configuração de atualizações do banco de dados](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/database-update) do sistema.
> Também [verifique a versão mínima do .NET exigida no Windows](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup/netframework-version).
1. Crie um backup do banco de dados e da pasta do site
1. Apague todos os arquivos da pasta do site **EXCETO OS ARQUIVOS** `AppSettings.config` e `Connections.config`
1. Extraia o conteúdo do novo pacote do Rest PKI ([restpki-1.33.9.zip](https://cdn.lacunasoftware.com/restpki/restpki-1.33.9.zip)) para a pasta do site
1. Acesse o site
> **Observação**
>
> Como o pacote não contém arquivos chamados AppSettings.config e Connections.config, os seus arquivos com esses nomes contendo as configurações do sistema serão preservados durante a operação.
Caso seja exibida a tela inicial com o título "REST PKI on premises", o processo estará concluído.
Caso o sistema esteja configurado para realizar atualizações manuais ao modelo do banco de dados, poderá ser necessário um procedimento adicional para atualizar o banco de dados.
> **Observação**
>
> Para saber com antecedência se uma atualização demandará atualização do banco de dados, observe no [Histórico de versões](https://docs.lacunasoftware.com/articles/rest-pki/changelog) se alguma das
> versões entre a versão que encontra-se instalada e a que você está instalando está marcada como demandando atualização do banco de dados. Para saber como
> verificar a versão instalada do sistema, veja [este artigo](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/check-version).
Nesse caso, será exibida a mensagem "HTTP Error 503.0 - The server is currently undergoing maintenance. Please try again later." ao acessar o sistema, conforme abaixo:

Realize então a [atualização manual do banco de dados](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/database-update#manual-update).
## Veja também
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/windows-setup/troubleshoot)
# [Resolução de problemas do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/troubleshoot)
Aqui estão reunidos artigos que podem auxiliar na resolução de problemas no uso do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki).
> **Dica**
>
> Os artigos de resolução de problemas são destinados a **desenvolvedores de software**. Se você é um usuário final
> tendo problemas para utilizar algum de nossos produtos, por favor visite a nossa [Central de Suporte](http://lacuna.help/)
Artigos:
* [Erro "Could not get pending signature"](https://docs.lacunasoftware.com/articles/rest-pki/troubleshoot/could-not-get-pending-sig)
# [Erro "Could not get pending signature"](https://docs.lacunasoftware.com/articles/rest-pki/troubleshoot/could-not-get-pending-sig)
Ao tentar implementar uma página com [assinatura web remota](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/remote) utilizando o
[Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) em conjunto com o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki), pode ocorrer um erro no navegador do
usuário final com a seguinte mensagem:
> Could not get pending signature
Nos exemplos de assinatura remota que utilizam a função `signWithRestPki` do Web PKI ocorre uma comunicação direta
entre o Web PKI e o Rest PKI para trafegar os dados da assinatura. Esse erro indica que essa comunicação não está
funcionando corretamente. Esse problema é comum quando se está utilizando uma instância própria do Rest PKI (*on
premises*), mas pode ocorrer também utilizando o Rest PKI em nuvem (em https://pki.rest/).
O primeiro passo para diagnosticar o problema é verificar se a máquina do usuário em questão tem acesso à
instância do Rest PKI sendo utilizada. Abra um navegador na máquina do usuário e acesse:
* Rest PKI em nuvem: https://restpki.lacunasoftware.com/Api/System/Info
* Rest PKI *on premises*: https://restpki.suaempresa.com.br/Api/System/Info (substitua de acordo com a URL da sua instância)
> **Observação**
>
> Embora a URL padrão para acessar as APIs do Rest PKI seja `https://pki.rest/`, no Web PKI utiliza-se por padrão a URL alternativa
> `https://restpki.lacunasoftware.com/` que, por ser mais convencional, tem uma chance menor de ser eventualmente bloqueada por
> firewalls das redes dos usuários finais.
O resultado esperado é um XML similar ao seguinte:
```xml
Lacuna Rest PKI
1.13.1
```
Caso a chamada falhe, o erro informado deve ajudar no diagnóstico do problema.
* Para Rest PKI em nuvem, a chamada provavelmente foi bloqueada pelo firewall da rede do usuário. Solicite ao responsável pela rede
em questão a liberação do domínio `restpki.lacunasoftware.com` em protocolo HTTPS.
* Para Rest PKI *on premises*, os principais motivos de falha são:
* A sua instância do Rest PKI não está disponível publicamente
* A sua instância do Rest PKI está disponível publicamente, porém está sendo bloqueada por um firewall no ambiente do usuário final. Nesse caso, para evitar
possíveis bloqueios, assegure-se que a URL do seu Rest PKI:
* Seja por hostname, não por IP (ex: `http://201.184.43.191/`)
* Seja via HTTPS com certificado de SSL válido
* Seja em porta padrão (443 ou, caso não esteja em HTTPS, porta 80)
Caso a chamada seja bem-sucedida, retornando um XML similar ao mostrado acima, realize na máquina onde está sendo observado o erro
o procedimento descrito na seção abaixo.
## Diagnóstico da extensão no Chrome
> **Observação**
>
> Caso o problema só ocorra em outros navegadores que não o Chrome, veja instruções no final do artigo
1. Clique com o botão direito no ícone do Web PKI (chip amarelo) no canto superior direito do navegador e clique na opção **Manage extensions**
1. Marque a caixa **Developer mode** no topo da lista de extensões
1. Localize o Web PKI na lista de extensões e clique no link **background page**
1. Na janela que se abrirá, clique na aba **Network**
1. Repita o procedimento de assinatura no navegador
1. Ficará registrado na aba Network a chamada de rede ao Rest PKI que falhou. Clique na chamada.
Após esse procedimento, você visualizará uma tela similar à seguinte:

Os detalhes do erro da chamada de rede ao Rest PKI devem indicar a causa do problema:
* Caso você esteja usando uma instância própria do Rest PKI ...
* ... e a chamada tenha sido para o **domínio `restpki.lacunasoftware.com` ao invés do domínio da sua instância do Rest PKI**: o endereço do
Rest PKI não está configurado corretamente no código Javascript da página. Veja a solução no artigo
[Especificando a URL do Rest PKI](https://docs.lacunasoftware.com/articles/web-pki/customizing-restpki-url).
* ... e a chamada tenha sido para o domínio correto da sua instância do Rest PKI ...
* ... e tenha falhado com **status code 404**: o endereço do Rest PKI pode não estar configurado corretamente no seu código de backend. Verifique
a inicialização da classe `RestPkiClient` (o nome da classe pode ser ligeiramente diferente dependendo da linguagem de programação).
* ... e tenha falhado com **outro status code**: tente diagnosticar o problema observando o código de erro e a mensagem. **NOTA:** certificados de
SSL inválidos causam falha silenciosa na comunicação entre o Web PKI e o Rest PKI. É fundamental que o certificado de SSL sendo utilizado esteja
dentro do período de validade e tenha sido emitido por uma Autoridade Certificadora confiada por padrão pelos sistemas operacionais.
* Caso você esteja usando a instância padrão do Rest PKI em `https://pki.rest/`: verifique o código de erro. Provavelmente o acesso ao Rest PKI está sendo
bloqueado pelo firewall da rede do usuário em questão. Se for o caso, entre em contato com o responsável pela rede em questão.
## Diagnóstico da extensão no Firefox
Para diagnosticar falhas na comunicação do Web PKI com o Rest PKI no Firefox, siga os passos abaixo:
1. Abra o menu do Firefox (ícone no canto superior direito)
1. Clique em **Add-ons**
1. Clique na engrenagem no topo da página, depois em **Debug Add-ons**
1. Marque a caixa **Enable add-on debugging**
1. Localize o Web PKI na lista de extensões e clique no link **Debug**
1. No pop-up que aparecerá, clique em **Allow**
1. Na janela que se abrirá, clique na aba **Network**
1. Repita o processo de assinatura no navegador
1. Ficará registrado na aba Network a chamada de rede ao Rest PKI que falhou. Clique na chamada.
> **Observação**
>
> No Firefox, ficam misturadas na aba *Network* da janela de debug da extensão as chamadas feitas pela página e feitas pela extensão. Para ajudar, a chamada
> que o Web PKI faz para o Rest PKI pode ser identificada pela rota, chamada **Certificate**
# [Autenticação com certificado - Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/certificate-authentication)
> **Aviso: Produto legado**
>
> O Rest PKI é um produto legado: continua recebendo correções de bugs e de segurança, mas novas integrações em nuvem
> devem usar o [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core), que também atende à maioria das APIs do Rest PKI. Esta página é para
> integrações existentes com o Rest PKI. Veja a [referência rápida do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference).
Os usuários entram na sua aplicação com o certificado digital: o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) emite um token de uso único, o
[Web PKI](https://docs.lacunasoftware.com/articles/web-pki) o assina com o certificado do usuário no navegador, e o Rest PKI verifica a assinatura e o certificado.
O código desta página é do [projeto de exemplos em ASP.NET MVC](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#auth): C# no servidor, com o `Lacuna.RestPki.Client`
1.24.1, e JavaScript no navegador, com o Web PKI.
## Fluxo
1. **Servidor**: inicie a autenticação com o contexto de segurança; o Rest PKI devolve um token.
1. **Navegador**: o Web PKI lista os certificados do usuário; quando ele escolhe um, `signWithRestPki()` assina com ele, usando o token.
1. **Servidor**: conclua a autenticação com o token; o Rest PKI devolve os resultados da validação e o certificado do usuário.
1. **Servidor**: se os resultados forem válidos, encontre o seu usuário por um campo do certificado e faça o login dele.
| Etapa | Método do cliente | Endpoint |
|---|---|---|
| Início | `StartWithWebPkiAsync(securityContextId)` | `POST Api/Authentications` |
| Conclusão | `CompleteWithWebPkiAsync(token)` | `POST Api/Authentications/{token}/Finalize` |
## 1. Iniciar a autenticação
O servidor inicia a autenticação e mostra a página com o token, uma string de 22 caracteres, que diferencia maiúsculas de minúsculas e é
segura para URLs. Ele não é o seu token de acesso à API.
```cs
var auth = Util.GetRestPkiClient().GetAuthentication();
var token = await auth.StartWithWebPkiAsync(Util.GetSecurityContextId());
base.SetNoCacheHeaders();
```
* O token vale para uma única autenticação. O sample envia headers que impedem o navegador de guardar a página em cache, para que o botão
voltar não mostre uma página com um token já usado.
* A autenticação precisa ser concluída em até uma hora por padrão; em uma instância on-premises, a configuração
`TimeToCompleteAuthentications` muda esse prazo (desde o Rest PKI 1.12.6).
* O contexto de segurança decide quais certificados são aceitos; os samples usam `LacunaTest` nos builds de debug, para aceitar os
[certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs), e `PkiBrazil` nos demais. Veja [Contexto de segurança](https://docs.lacunasoftware.com/articles/rest-pki/validate-signatures#contexto-de-segurança).
* Para ignorar erros por status de revogação desconhecido, inicie com um `AuthenticationOptions` cujo `IgnoreRevocationStatusUnknown` seja
`true` (desde o Rest PKI 1.16.0); o sample não faz isso.
## 2. Assinar o token no navegador
A página guarda o token em um campo oculto, com um `` para os certificados e um botão **Sign In** que não é de envio: o formulário
é enviado por script quando o Web PKI termina.
O script inicializa o Web PKI com o endpoint do seu Rest PKI em `restPkiUrl`:
```js
var pki = new LacunaWebPKI(_webPkiLicense);
pki.init({
ready: loadCertificates, // As soon as the component is ready we'll load the certificates.
defaultError: onWebPkiError, // Generic error callback defined below.
restPkiUrl: _restPkiEndpoint // REST PKI endpoint to communication between Web PKI.
});
```
Quando o usuário clica em **Sign In**, o script chama `signWithRestPki()` com o token e o certificado escolhido e depois envia o
formulário:
```js
var selectedCertThumbprint = formElements.certificateSelect.val();
pki.signWithRestPki({
token: formElements.token,
thumbprint: selectedCertThumbprint
}).success(function () {
formElements.form.submit();
});
```
* `_webPkiLicense` e `_restPkiEndpoint` vêm das configurações `WebPkiLicense` e `RestPkiEndpoint`. `RestPkiEndpoint` só é necessário
on-premises, e uma licença do Web PKI só para rodar em um host diferente de `localhost`.
Veja também [Especificando a URL do Rest PKI](https://docs.lacunasoftware.com/articles/web-pki/customizing-restpki-url).
* O Web PKI troca os dados da assinatura diretamente com o Rest PKI.
## 3. Concluir a autenticação
De volta ao servidor, conclua a autenticação com o token. Se os resultados não forem válidos, mostre o que falhou; caso contrário, leia o
certificado do usuário:
```cs
var auth = Util.GetRestPkiClient().GetAuthentication();
var validationResults = await auth.CompleteWithWebPkiAsync(model.Token);
if (!validationResults.IsValid) {
return View("Failed", validationResults);
}
return View("Success", new AuthenticationInfoModel() {
UserCert = auth.GetCertificate()
});
```
* O Rest PKI responde HTTP 200 tenha a autenticação dado certo ou não: verifique `IsValid`. `ValidationResults.ToString()` lista todas as
verificações, e o sample a mostra na página de falha.
* A conclusão remove a autenticação: uma segunda chamada com o mesmo token falha.
* Se o navegador não assinou, a conclusão falha com `ClientSignatureNotSet` (HTTP 422). Veja os [códigos de retorno](https://docs.lacunasoftware.com/articles/rest-pki/return-codes).
* `GetCertificate()` só pode ser chamado depois de um método de conclusão.
## 4. Fazer o login do usuário
Quando os resultados são válidos, o comentário do sample diz que o certificado é válido segundo o contexto de segurança escolhido no início
e que o usuário é o titular do certificado. Procure o seu usuário por um dos campos do certificado, como `EmailAddress` ou, em certificados ICP-Brasil,
`PkiBrazil.Cpf`; qual campo usar depende da sua aplicação.
O certificado (`PKCertificate`) tem `SubjectName`, `EmailAddress`, `IssuerName`, `SerialNumber`, `ValidityStart`, `ValidityEnd`,
`ThumbprintSHA256` e, em certificados ICP-Brasil, `PkiBrazil` com `CertificateType`, `Cpf`, `Cnpj`, `Responsavel`, `CompanyName`,
`DateOfBirth` e os campos de RG e OAB.
TODO(docs): o que o Rest PKI verifica na conclusão, além da confiança no certificado segundo o contexto de segurança.
## Erros
* Quando um limite da conta está definido e é excedido, o Rest PKI responde HTTP 429 com os headers `X-RateLimit-Limit` e
`X-RateLimit-Remaining`.
* Para os demais códigos, veja os [códigos de retorno](https://docs.lacunasoftware.com/articles/rest-pki/return-codes) e a [referência rápida](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference).
## Veja também
* [Validar assinaturas](https://docs.lacunasoftware.com/articles/rest-pki/validate-signatures)
* [Projeto de exemplos em ASP.NET MVC](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#auth)
* [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
**Demonstrações e exemplos**
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Authenticate with a digital certificate using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/AuthenticationRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/authentication-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/authentication-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/authentication_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/authentication_rest_controller.rb)
# [Histórico de versões do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/changelog)
### 1.33.9 (2026-09-08)
Atualização do modelo do banco de dados: Não
- Atualização PKI SDK para [2.22.5](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-22-5)
### 1.33.8 (2026-04-07)
Atualização do modelo do banco de dados: Não
- Atualização PKI SDK para [2.22.3](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-22-3) com correção de loop de validação de TSL.
### 1.33.7 (2026-03-05)
Atualização do modelo do banco de dados: Não
- Atualização PKI SDK para [2.22.2](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-22-2) com correção na adição de tag T8 em PDF.
### 1.33.6 (2025-11-28)
Atualização do modelo do banco de dados: Não
- Atualização PKI SDK para [2.21.2](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-21-2)
### 1.33.5 (2025-10-20)
Atualização do modelo do banco de dados: Não
- Adiciona suporte à carimbadora SERPRO com novo protocolo oauth
- Adiciona parâmetro `ArchiveTimestamps` no model dos detalhes das assinaturas
### 1.33.3 (2025-05-02)
Atualização do modelo do banco de dados: Não
- Adiciona decodificação de algoritmos de assinatura ECDsa
- Adiciona formatação em string do DN (Distinguished Name) do certificado no "Name model"
- Atualização do contexto de segurança Lacuna Test (para teste e desenvolvimento apenas)
### 1.33.2 (2023-05-22)
Atualização do modelo do banco de dados: Não
- Atualização PKI SDK para [2.13.1](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-13-1)
### 1.33.0 (2023-05-06)
Atualização do modelo do banco de dados: Não
- Adiciona política de assinatura Cades: ICP-Brasil Ref. Tempo (CAdES AD-RT) without CRLs
### 1.32.2 (2022-10-01)
Atualização do modelo do banco de dados: Não
- Melhora no suporte de autenticação via "basic authentication" com carimbadoras de tempo
### 1.32.1 (2022-09-18)
Atualização do modelo do banco de dados: Não
- Adiciona suporte ao protocolo proprietário da carimbadora de tempo Serpro
### 1.32.0 (2022-09-12)
Atualização do modelo do banco de dados: Não
- Adiciona política de assinatura XML (NFe) com hash SHA-256
### 1.31.0 (2022-09-04)
Atualização do modelo do banco de dados: Não
- Adiciona hash do "access token" do usuário às informações de transação da API
### 1.30.1 (2022-05-09)
Atualização do modelo do banco de dados: Não
- Atualização PKI SDK para [2.11.5](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-11-5)
### 1.30.0 (2021-11-28)
Atualização do modelo do banco de dados: Não
- Adiciona propriedade thumbprint SHA-256 no *certificate summary model*
- Melhora política de assinatura CAdES ICP-Brasil AD-RA
- Atualiza o PKI SDK para [2.9.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-9-0)
### 1.27.0 (2021-08-31)
Atualização do modelo do banco de dados: Não
- Adicionar políticas de assinatura XAdES ICP-Brasil AD-RC e AD-RA
- Atualização PKI SDK para [2.8.12](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-8-12)
### 1.26.7 (2021-08-02)
Atualização do modelo do banco de dados: Não
- Adicionar verificação de presença de LTV no PadesSignerInfo
- Adicionar aplicação de limitador em operação de marcas de PDF
- Atualização PKI SDK para [2.8.10](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-8-10)
### 1.26.6 (2021-07-16)
Atualização do modelo do banco de dados: Não
- Atualização PKI SDK para [2.8.9](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-8-9)
### 1.26.5 (2021-07-08)
Atualização do modelo do banco de dados: Não
- Adicionar tempo limite configurável no comando de criação de fatura no banco de dados
### 1.26.4 (2021-07-04)
Atualização do modelo do banco de dados: Não
- Adicionar opção de alinhamento vertical nos elementos de texto da funcionalidade PDF marks
- Adicioanr suporte a identidades de mais países (Argentina, Equador, Paraguai e Peru) na tag de representação visual PAdES [`{{national_id}}`](https://docs.lacunasoftware.com/articles/rest-pki/pades-tags)
- Adicioanr suporte a tag de dados de CPF e RG mascarados na representação visual PAdES: [`{{br_cpf_masked}} e {{br_rg_numero_masked}}`](https://docs.lacunasoftware.com/articles/rest-pki/pades-tags)
### 1.26.3 (2021-05-17)
Atualização do modelo do banco de dados: Não
- Atualização PKI SDK para [2.8.7](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-8-7)
- Melhora o tratamento de erros relacionados a PDFs protegidos por senha para retornar o código de status 422 em vez de 500
### 1.26.2 (2021-03-31)
Atualização do modelo do banco de dados: Não
- Atualização PKI SDK para [2.8.4](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-8-4)
### 1.26.1 (2021-03-22)
Atualização do modelo do banco de dados: Não
- Melhorar políticas permissivas para validação
- Corrigir exceção não tratada na abertura de uma assinatura PAdES sem o valor do digest
- Atualização PKI SDK para 2.8.1
### 1.26.0 (2021-03-11)
Atualização do modelo do banco de dados: Não
- Adicionar API para abertura e validação de um certificado
### 1.25.3 (2021-02-10)
Atualização do modelo do banco de dados: Não
- Disabilitar tasks de atualização e limites de número de transações em instâncias on-premises
### 1.25.2 (2021-02-09)
Atualização do modelo do banco de dados: Não
- Alongar cronograma de execução da task de atualização de número de transações para cada 4 horas
### 1.25.1 (2020-12-15)
Atualização do modelo do banco de dados: Não
- Adicionar política "CmsPermissive" para validação de assinaturas CAdES
### 1.25.0 (2020-12-06)
Atualização do modelo do banco de dados: Não
- Adicionar campo "Reason" em assinaturas PAdES
### 1.24.3 (2020-09-22)
Atualização do modelo do banco de dados: Não
- Melhora página inicial
### 1.24.1 (2020-09-16)
Atualização do modelo do banco de dados: Não
- Melhorar política de validação "Adobe Reader" para lidar com o erro "forbidden signed attribute is present"
- Atualização PKI SDK para [2.7.8](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-7-8)
### 1.24.0 (2020-07-09)
Atualização do modelo do banco de dados: Não
- Adicionar campo "CertificationLevel" em assinaturas PAdES
### 1.23.0 (2020-06-19)
Atualização do modelo do banco de dados: Não
- Corrigir exceção que ocorre ao abrir uma assinatura PAdES com o campo "SignaturePolicy" sem a URI
- Adicionar campo "CustomSignatureFieldName" em assinaturas PAdES
- Atualização PKI SDK para [2.7.2](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-7-2)
### 1.22.1 (2020-04-22)
Atualização do modelo do banco de dados: Não
- Melhorar tratamento do erro quando o usuário fornece um token inválido
### 1.22.0 (2020-02-28)
Atualização do modelo do banco de dados: Não
- Adicionar política de assinatura PAdES sem LTV
- Adicionar opção "PreserveSignaturesVisualRepresentation" na adição de marcas de PDF
- Adicionar opção "TrustUncertifiedSigningTime" na validação de assinaturas PAdES e CAdES
- Adicionar política de validação para assinaturas feitas no Adobe Reader
- Adicionar configuração para limitar a quantidade (*throttling*) de requisições feitas à API
### 1.21.1 (2020-02-21)
Atualização do modelo do banco de dados: Não
- Atualização PKI SDK para [2.5.5](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-5-5)
### 1.21.0 (2020-01-10)
Atualização do modelo do banco de dados: Não
- Adicionar uma política de assinatura CAdES ICP-Brasil sem LCRs
### 1.20.2 (2019-07-21)
Atualização do modelo do banco de dados: Não
- Corrija o problema de alto uso da CPU que ocorreu em determinadas condições
- Melhore a programação do carimbo de data e hora para tentar solicitantes marcados como indisponíveis (com menos prioridade)
- Adicionar configurações `ProcessBackgrounJobs` e `WatchTimestampRequesters`
- Atualização PKI SDK para [2.5.2](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-5-2)
### 1.20.1 (2019-07-04)
Atualização do modelo do banco de dados: Não
- Adicionar direções de posicionamento automático do PAdES (`LeftToRight`/`RightToLeft` e `TopDown`/`BottomUp`)
- Adiconar suporte para integração Lapp (*Lapp* é um Provedor de Artefato para PKI, usado como um proxy CRL)
- Atualização PKI SDK para [2.5.1](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-5-1)
### 1.20.0
*Nenhuma mudança (version ignorada)*
### 1.19.1 (2019-07-03)
Atualização do modelo do banco de dados: Não
- Melhora os trabalhos de manutenção
### 1.19.0 (2019-03-15)
Atualização do modelo do banco de dados: Não
- Integração refatorada com B-Stamper
- Atualização PKI SDK para [2.2.8](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-2-8)
### 1.18.5 (2019-01-03)
Atualização do modelo do banco de dados: Não
- Melhorias de desempenho no faturamento (não afeta as instalações locais)
### 1.18.4 (2018-12-11)
Atualização do modelo do banco de dados: Não
- Melhora as validações nas assinaturas PAdES e CAdES que exploram APIs para retornar o código de status 422 em vez de 500
- Atualização PKI SDK para [2.2.3](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-2-3)
### 1.18.3 (2018-10-23)
Atualização do modelo do banco de dados: Não
- Adiciona suporte para assinatura padrão XML *Certificación de Origen Digital* (COD),
da *Asociación Latinoamericana de Integración* (ALADI). Para maiores informações, veja o padrão em [Espanhol](http://www.aladi.org/nsfweb/Documentos/2327Rev2.pdf) ou em
[Português](http://www.mdic.gov.br/images/REPOSITORIO/secex/deint/coreo/2014_09_19_-_Brasaladi_761_-_Documento_ALADI_SEC__di_2327__Rev_2_al_port_.pdf)
- Atualização PKI SDK para [2.2.2](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-2-2), corrigindo erros na rotação das marcas PDF
### 1.18.2 (2018-09-26)
Atualização do modelo do banco de dados: Não
- Correção de bugs que impediram o carregamento correto da cadeia de certificados em determinados cenários:
- Nas assinaturas do CAdES, sempre que o último link (para a CA raiz) não podia ser construído automaticamente
- Em todas assinaturas XML
### 1.18.1 (2018-09-11)
Atualização do modelo do banco de dados: Não
- Melhora instrumentação Insights de Aplicação
- Adicionado suporte para especificar a chave da API do AI, além da chave da instrumentação
### 1.18.0 (2018-08-13)
Atualização do modelo do banco de dados: **Sim**
- Adicionado suporte para configurar um ID da política TSA em solicitações de carimbo de data e hora de saída
- Atualização PKI SDK para [2.1.3](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-1-3)
### 1.17.5 (2018-08-01)
Atualização do modelo do banco de dados: Não
- Correção de bug metadados Swagger
- Melhoria de usuários API
### 1.17.4 (2018-07-11)
Atualização do modelo do banco de dados: Não
- Corrigido o erro que afeta as operações executadas com contextos de segurança que incluem o PKI Italy
- Melhorar a API da fatura
### 1.17.3 (2018-05-24)
Atualização do modelo do banco de dados: Não
- Adicionar link para a [política de privacidade](https://docs.lacunasoftware.com/articles/rest-pki/privacy-policy) na tela inicial ([GDPR](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation) conformidade)
### 1.17.2 (2018-05-15)
Atualização do modelo do banco de dados: Não
- Atualização Lacuna PKI SDK para 1.20.16, portanto:
- Corrigido erro de preenchimento da cadeia de certificados no PAdES
### 1.17.1 (2018-05-09)
Atualização do modelo do banco de dados: Não
- Atualização Lacuna PKI SDK para 1.20.15, portanto:
- Melhoria na tolerância para certificados mal formados (corrigindo problemas devido a recentes certificados mal formados do SERPRO)
- Atualização intermediação ICP-Brasil CAs
### 1.17.0 (2018-03-29)
Atualização do modelo do banco de dados: Não
- Melhoria no dashboard
- Melhorias gerais no faturamento
### 1.16.0 (2017-12-12)
Atualização do modelo do banco de dados: **Sim**
- Adicionado suporte para ignorar erros de validação de certificado devido a status de revogação desconhecido
- Adicionado suporte para ativar o Lacuna Test PKI em instâncias locais
- Adicionado configuração da chave B-Stamper API
- Melhorar a instrumentação de solicitações de TSP
- Correção do bug que causava falha nas assinaturas se o servidor `catest.lacunasoftware.com` estivesse inacessível
- Correção de pequenos bugs na API de faturas
### 1.15.2 (2017-11-20)
Atualização do modelo do banco de dados: Não
- Adicionado informações de B-Stamp na resposta de APIs de assinatura aberta
- Melhore a geração do pacote de auditoria para retornar o link de download em vez do conteúdo
- Correção de bug no B-Stamping de assinaturas enviadas para as APIs de assinatura aberta
- Correção da validação de assinaturas com B-Stamped
- Correção de bug na atualização do modelo de banco de dados
- Atualização Lacuna PKI SDK para 1.20.5
### 1.15.1 (2017-11-16)
Atualização do modelo do banco de dados: Não
- Correção do erro de validação do modelo que poderia causar uma resposta 400 (BadRequest) na API de marcas de PDF
**Problemas conhecidos:**
- Uma exceção pode ser lançada durante a atualização do modelo de banco de dados (corrigida em 1.15.2)
### 1.15.0 (2017-11-14)
Atualização do modelo do banco de dados: **Sim**
- Adicionado integração B-Stamper
- Adicionado
- Melhoria nas validações na API de assinaturas do PAdES para retornar os códigos de status 400/422 em vez de 500
- Melhoria na instrumentação do Application Insights
- Adicionar campo UserId
- Correção de bug que fazia com que 422 respostas fossem registradas sem o campo ErrorCode
- Corrigido o erro de front-end que sugeria que os usuários pudessem atualizar seu próprio CustomerId
**Problemas conhecidos:**
- Uma exceção pode ser lançada durante a atualização do modelo de banco de dados (corrigida em 1.15.2)
### 1.14.4 (2017-09-29)
Atualização do modelo do banco de dados: Não
- Adicionada opção para exportar relatório de cobrança para CSV
- Melhorar a validação de URLs de arquivos externos transmitidos para a API
- Melhorar a instrumentação
- Registrando campos de erro da API no Application Insights
- Aumentar a verbosidade do nível de log "Debug"
- Adicionado eventos para ajudar a diagnosticar problemas na integração com a PKI da Web
- Correção de bug que poderia causar várias tarefas em segundo plano para permanecer na fila
- Correção de bug que causava uma NullReferenceException (manipulada) durante a inicialização do sistema
- Correção de bug na aplicação do limite de transações
- Atualize o Lacuna PKI SDK para 1.19.4
### 1.14.3 (2017-09-17)
Atualização do modelo do banco de dados: **Sim**
- Corrigir compatibilidade com o PostgreSQL (as versões 1.14.x anteriores não eram compatíveis)
- Melhorar o gerenciamento do solicitante de carimbo de data e hora
- Melhorar validações na API de marcas PDF
- Reduza arquivos não usados em instalações locais
- Correção do bug que fazia com que os solicitantes de carimbo de data e hora fossem marcados como indisponíveis durante a atualização do banco de dados
- Correção do bug que fazia com que os certificados CA sem um campo CN (nome comum) fossem exibidos incorretamente
- Correção de bug no carregamento da página de limites do usuário
### 1.14.2 (2017-09-12)
Atualização do modelo do banco de dados: Não
- Correção de bug que fazia com que solicitações bem-sucedidas de TSP fossem registradas como falhas no Azure Application Insigh
**Problemas conhecidos:**
- A versão não pode ser instalada no PostgreSQL (corrigida na 1.14.3)
### 1.14.1 (2017-09-11)
Atualização do modelo do banco de dados: Não
- ~~Correção de bug que fazia com que solicitações bem-sucedidas de TSP fossem registradas como falhas no Azure Application Insigh~~ (realmente corrigido apenas em 1.14.2)
**Problemas conhecidos:**
- A versão não pode ser instalada no PostgreSQL (corrigida na 1.14.3)
### 1.14.0 (2017-09-09)
Atualização do modelo do banco de dados: **Sim**
- Adicionado limites de transação (em todo o sistema ou por usuário)
- Adicionado suporte para administradores para bloquear usuários
- Melhoria do fallback e a recuperação do solicitante de carimbo de data e hora
- Adicionado várias validações de API para retornar os códigos de status 400 e 422 em vez de 500
- Adicionado suporte para administradores para gerenciar ACs intermediárias personalizadas dos usuários
- Adicionado a opção "Windows Server" nas PKIs padrão adicionais dos contextos de segurança personalizados
- Adicionado um aviso no painel se a licença do PKI SDK estiver próxima do vencimento
- Melhorias no relatório de cobrança
- Correção de bug nos botões de paginação
- Adicionado coluna com marcas de PDF
- Adicionar linha de totais
- Melhorias na instrumentação
- As operações que diferem apenas por parâmetro na rota agora são unificadas nas informações do aplicativo
- As solicitações de TSP com falha agora são registradas como informações do aplicativo
- Os parâmetros de confiança da API (contexto de segurança e política de assinatura) agora estão registrados nos eventos
- Adicionar rastreamento de lib do cliente
- Correção de bug que fazia com que configurações sensíveis fossem gravadas no log do sistema
- Correção de bug que impedia a exclusão de solicitantes de carimbo de data e hora
- Corrija o nome da tag PAdES `{{br_oab_numero}}` (o antigo `{{br_oab_numbero}}` ainda é suportado)
**Problemas conhecidos:**
- A versão não pode ser instalada no PostgreSQL (corrigida na 1.14.3)
### 1.13.3 (2017-09-07)
Atualização do modelo do banco de dados: Não
- Atualize o Lacuna PKI SDK para 1.19.3 para corrigir erros na verificação do status de revogação
- Resultados completos da validação agora incluídos no evento de autenticação de certificado concluída
- Logs aprimorados para ajudar no diagnóstico de erros de parametrização de confiança
### 1.13.2 (2017-08-28)
Atualização do modelo do banco de dados: Não
- Pequenas alterações na API de cobrança
### 1.13.0 (2017-07-03)
Atualização do modelo do banco de dados: Não
- Adicionado uma nova API para adicionar marcas aos PDFs
- Adicionado suporte aos administradores para gerenciar os contextos de segurança personalizados dos usuários
- Melhore as telas de transações e eventos
### 1.12.8 (2017-06-27)
Atualização do modelo do banco de dados: Não
- Correção de bug que fazia com que ACs intermediárias fossem ignoradas em APIs de assinatura abertas / validadas
### 1.12.7 (2017-06-05)
Atualização do modelo do banco de dados: **Sim**
- Correção de bug que causava conflitos de banco de dados em cenários de alta carga
- Correção de bug que causava erros em timestampers criados recentemente
### 1.12.6 (2017-06-01)
Atualização do modelo do banco de dados: Não
- Criação das configurações do aplicativo TimeToCompleteSignatures e TimeToCompleteAuthentications
### 1.12.5 (2017-05-11)
Atualização do modelo do banco de dados: Não
- Adicionado a configuração opcional do aplicativo DefaultTimestampTimeout para personalizar o tempo limite padrão ao solicitar registros de data e hora
- Melhore os logs de desempenho
- Atualize o Lacuna PKI SDK para 1.16.7 para melhorar os logs de download
### 1.12.4 (2017-05-05)
Atualização do modelo do banco de dados: Não
- Correção de bug que fazia com que privilégios de execução sobre a pasta do site fossem solicitados no aplicativo
### 1.12.3 (2017-05-04)
Atualização do modelo do banco de dados: Não
- Correção de bug na configuração da configuração "Is on premises"
- Correção de bug na dica de configuração "Cryptographic keys"
- Correção de bug na API de assinaturas XML de abertura/validação
### 1.12.2 (2017-04-26)
Atualização do modelo do banco de dados: Não
- Correção de bug na autentificação do certificado com Web PKI
### 1.12.1 (2017-04-26)
Atualização do modelo do banco de dados: Não
- Correção de um erro na inicialização do aplicativo que poderia causar o processamento duplo de trabalhos em segundo plano
### 1.12.0 (2017-04-25)
Atualização do modelo do banco de dados: **Sim**
- Adicionado suporte para emissão de registros de data e hora por meio da API REST
- Adicionado uma nova API para abrir/validar assinaturas XML
- Adicionado instrumentação opcional com o Application Insights
- Adicionado a configuração opcional do aplicativo "AutoUpdateDatabase" para controlar se o aplicativo tentará atualizar automaticamente o banco de dados
- Adicionado a configuração opcional do aplicativo "AllowInsecureMaintenance" para permitir acesso remoto às páginas de manutenção por HTTP
- Correção de vários problemas que ocorrem ao executar várias instâncias do aplicativo (cenários de alta disponibilidade)
- ~~Correção de problemas de simultaneidade do banco de dados que ocorrem em situações de alta carga~~ (realmente corrigido apenas na 1.12.7)
- Correção do bug que ocorreu quando a licença do Lacuna PKI SDK foi alterada
- Atualização do Lacuna PKI SDK para 1.16.6
### 1.11.4 (2017-03-16)
Atualização do modelo do banco de dados: Não
- Atualização do Lacuna PKI SDK para 1.16.5, assim:
- ACs intermediárias do ICP-Brasil atualizadas
- Alterações na inspeção de assinatura do CAdES para lidar com registros de data e hora codificados incorretamente
### 1.11.3 (2017-02-13)
Atualização do modelo do banco de dados: Não
- Alteração da política XML "Nota Fiscal Eletrônica (NF-e)" para excluir os IDs dos elementos de assinatura
- Atualização o Lacuna PKI SDK para 1.16.2
### 1.11.2 (2017-01-20)
Atualização do modelo do banco de dados: Não
- Correção de bug na página de manutenção do banco de dados
### 1.11.1 (2017-01-17)
Atualização do modelo do banco de dados: Não
- Correção de bug nas rotas de upload e download
### 1.11.0 (2017-01-17)
Atualização do modelo do banco de dados: **Sim**
- Adicionado suporte para upload de arquivos com várias partes
- Adicionado suporte para assinar arquivos enviados anteriormente, arquivos de assinaturas anteriores e arquivos remotos
- Adicionado suporte para baixar o arquivo assinado em uma solicitação HTTP separada ("link para download")
- Adicionado suporte para novas tags de representação visual do PAdES (consulte [tags PAdES](https://docs.lacunasoftware.com/articles/rest-pki/pades-tags))
- Adicionado suporte para decodificar todos os campos nos nomes X.509
- Adicionado a política do CAdES "Assinatura eletrônica básica (CAdES-BES) com horário de assinatura e sem CRLs"
- Correção de bug que impedia que as CAs intermediárias do sistema entrassem em vigor
- Correção de bug que redefinia o serviço de armazenamento para local sempre que o aplicativo era reiniciado
- Atualização do Lacuna PKI SDK para 1.16.0, assim:
- Melhora dos campos do ICP-Brasil. Os campos também retornam nulos se tiverem valor de preenchimento padrão
- Melhora da assinatura do Pades para carregar e validar assinaturas PDF adbe.pkcs7.sh1
- Melhora downloads internos
- Correção do estado TSL UnderSupervision como um estado válido
- Correção do atraso de carregamento do armazenamento de certificados devido à tentativa de carregamento da cadeia de certificados
- Correçaõ da codificação de carimbo de data e hora do arquivo CAdES
- Correção da linha vazia da representação visual do PAdES
- Correçaõ do posicionamento incorreto das marcas de PDF e da representação visual do PAdES em alguns PDFs rotacionados
- Correção da exceção em alguns campos do ICP-Brasil decodificar
- Correção de bug que ocorre em algumas assinaturas de PDF
- Correção da ordem de atributo das referências de revogação completas do CAdES
- Atualização do uri e o root do TSL italiano
### 1.10.1 (2016-10-01)
Atualização do modelo do banco de dados: Não
- Adicionado políticas PAdES padrão "Básico com certificados ICP-Brasil" e "PAdES-T com certificados ICP-Brasil"
### 1.10.0 (2016-09-27)
Atualização do modelo do banco de dados: **Sim**
- Adicionado suporte para executar assinaturas desanexadas do CAdES sem fazer upload do arquivo de dados
- Melhorar o gerenciamento de usuários
### 1.9.1 (2016-09-22)
Atualização do modelo do banco de dados: Não
- Correção de um erro que impedia o uso das tags `{{signerEmail}}` e `{{issuerCommonName}}` na representação visual do PAdES
### 1.9.0 (2016-08-25)
Atualização do modelo do banco de dados: **Sim**
- Adicionado suporte para marcas de PDF
- Adicionado suporte para obter o arquivo encapsulado ao abrir uma assinatura CAdES
- Melhorar o relatório de cobrança
### 1.8.2 (2016-07-19)
Atualização do modelo do banco de dados: Não
- Correção de bug nas assinaturas do CAdES com registro de data e hora
### 1.8.1 (2016-07-11)
Atualização do modelo do banco de dados: Não
- Correção de bug que afeta algumas assinaturas de PDFs grandes
- Atualização do Lacuna PKI SDK para 1.13.1
### 1.8.0 (2016-07-05)
Atualização do modelo do banco de dados: Não
- Adicionado uma nova API para abrir/validar assinaturas CAdES e PAdES
- Adicionado suporte às novas políticas de assinatura do ICP-Brasil PAdES AD-RB e AD-RT
- Adicionado relatório de histórico de transações
- Adicionado suporte para as tags `{{signerEmail}}` e `{{issuerCommonName}}` na representação visual do PAdES
- Adicionado a propriedade BinaryThumbprintSHA256 a CertificateModel
- Adicionado suporte para criar usuários sem senha (útil para usuários de aplicativos)
- Adicionado um aviso ao status do sistema ao alterar o armazenamento quando já houver blobs armazenados
- Aumento do tamanho máximo da solicitação de API para 100 MB
- Correção de bug que impedia a co-assinatura do CAdES
- Correção do erro de autorização na rota de download dos certificados raiz confiáveis dos contextos de segurança
- Atualização o Lacuna PKI SDK para 1.13.0
### 1.7.2 (2016-06-10)
Atualização do modelo do banco de dados: Não
- Adicionado detecção de dados incorretos transmitidos por aplicativos clientes:
- PDFs inválidos / corrompidos
- Parâmetros de representação visual inválidos do PAdES
- Correção de bug na remoção do contexto de segurança
- Correção de bug que deixa registros ClientSideSignature não utilizados deixados no banco de dados
- Correção de bug que impedia os usuários administradores de visualizarem detalhes dos eventos de outros usuários
- Atualização do Lacuna PKI SDK para 1.12.2 (sem correções)
### 1.7.1 (2016-06-03)
Atualização do modelo do banco de dados: Não
- Correção de bug no download do certificado raiz confiável de um contexto de segurança
### 1.7.0 (2016-06-03)
Atualização do modelo do banco de dados: **Sim**
- Adicionado suporte para registro de data e hora
- Adicionado suporte para contextos de segurança em todo o sistema
- Adicionado suporte para políticas de assinatura personalizadas em todo o sistema
- Adicionado suporte para opções adicionais de armazenamento para arquivos temporários criptografados (o armazenamento no sistema de arquivos local ainda é suportado):
- Armazenamento de Blobs do Microsoft Azure
- Amazon S3
- Adicionado suporte para administradores para gerar tokens de acesso para outros usuários
- Adicionado suporte aos administradores para visualizar os eventos de outros usuários ("histórico")
- Adicionado suporte aos campos de certificado ICP-Brasil "RG" e "OAB"
- Atualização do Lacuna PKI SDK para 1.12.1, assim:
- Adicionado suporte para certificados com alternativa rara SHA-1 com algoritmo de assinatura RSA OID (1.3.14.3.2.29)
### 1.6.4 (2016-05-05)
Atualização do modelo do banco de dados: **Sim**
- Armazenar arquivos criptografados temporários no diretório temporário local em vez de armazenar em colunas binárias no banco de dados
- Essa alteração foi feita devido a problemas de desempenho. A próxima versão adicionará suporte para outras opções de armazenamento.
### 1.6.3 (2016-05-02)
Atualização do modelo do banco de dados: **Sim**
- Adicione suporte para personalização de cultura, formato e fuso horário do horário de assinatura na representação visual do PAdES
- Atualize o Lacuna PKI SDK para 1.12.0, assim:
- Correção de um erro na codificação da estrutura ASN.1 AlgorithmIdentifier, que fazia com que o campo "parameters" fosse omitido de ser preenchido com o valor NULL
- Não está mais usando a biblioteca licenciada iTextSharp AGPL
- Correção de bug na validação do status de revogação de certificado que causava um estouro de pilha em cenários raros de validação do OCSP
- Correção de bug na decodificação da CRL quando o ReasonCode está presente
- Melhorar mensagens para validação do status de revogação de certificado
- Corrigir problema que afeta a validação de assinaturas XML com declarações de namespace no elemento Signature
- Corrigir problema que afeta o posicionamento das representações visuais do PAdES em cenários específicos de vários assinantes
- Adicionada a raiz confiável do ICP-Brasil "v5"
### 1.6.2 (2016-04-18)
Atualização do modelo do banco de dados: Não
- Adicionado suporte à representação visual do PAdES para especificar um contêiner dentro do retângulo de assinatura no qual colocar o texto
### 1.6.1 (2016-02-22)
Atualização do modelo do banco de dados: Não
- Correção de bug que causa delay no acesso do banco de dados quando está executando no Microsoft Azure
### 1.6.0 (2016-01-21)
Atualização do modelo do banco de dados: **Sim**
- Adicionado suporte para assinaturas XML (XmlDSig/XAdES)
- Atualização do Lacuna PKI SDK para 1.9.0, assim:
- Melhoria na validação dos certificados para checar a extensão PathLenConstraint
### 1.5.1 (2015-11-24)
Atualização do modelo do banco de dados: Não
- Somente mudanças estéticas
### 1.5.0 (2015-11-23)
Atualização do modelo do banco de dados: **Sim**
- Adicionado suporte para assinaturas do CAdES
- Adicionado registro de transação (para faturamento futuro)
- Adicionado suporte para licenças Lacuna PKI SDK com uso restrito ao REST PKI
- Melhora na remoção de processos de assinatura expirados
- Atualização do Lacuna PKI SDK para 1.8.0, assim:
- Modificação do comportamento de decodificação dos campos de certificado do ICP-Brasil para decodificar campos, independentemente de o certificado parece ser um certificado ICP-Brasil ou não
- Modificação do comportamento de decodificação do campo de certificado CompanyName ICP-Brasil para retornar o nome da empresa quando o certificado for um certificado de empresa ICP-Brasil (PJ) (anteriormente a propriedade trabalhava apenas para certificados de aplicação ICP-Brasil)
- Adicionado suporte para o campo CPF do ICP-Brasil no campo "OU" do nome do sujeito com espaço após os dois pontos ("OU = CPF: xxxxxxxxxxx")
### 1.4.3 (2015-11-06)
Atualização do modelo do banco de dados: Não
- Somente mudanças estéticas
### 1.4.2 (2015-11-06)
Atualização do modelo do banco de dados: Não
- Somente mudanças estéticas
### 1.4.1 (2015-10-21)
Atualização do modelo do banco de dados: Não
- Somente mudanças estéticas
### 1.4.0 (2015-10-21)
Atualização do modelo do banco de dados: Não
- Adicionado suporte à representação visual do PAdES para alinhamento horizontal de texto à direita
- Definição da cultura do site como pt-BR (afeta a representação visual do PAdES)
- Correção do bug nas assinaturas PAdES
### 1.3.1 (2015-10-14)
Atualização do modelo do banco de dados: Não
- Melhora no registro em arquivo para impedir o crescimento indefinido de arquivos
- Adicionado informações sobre a licença Lacuna PKI SDK na tela de status do sistema
- Correção um pequeno problema no javascript quando o Google Analytics não estiver sendo usado
- Correção um problema menor na caixa de diálogo de teste de log
- Atualização do Lacuna PKI SDK para 1.6.0, assim:
- Correção o erro no log que fazia com que o argumento "source" tivesse um valor incorreto
### 1.3.0 (2015-10-13)
- Primeira versão divulgada publicamente
- Principais recursos desta versão:
- Autenticação de certificado
- Assinaturas PAdES
# [Rest PKI - Configuração de Data e Hora](https://docs.lacunasoftware.com/articles/rest-pki/configure-date)
É possível alterar o formato e fuso horário das datas que aparecem nas assinaturas de PDF feitas com o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki).
Para alterar essa configuração, siga os passos abaixo:
1. Autentique-se em https://pki.rest/
1. No menu superior, clique no seu email e em **Conta** ou *Account*

1. Na tela seguinte, utilize os controle na seção **Região e Data** ou *Region and Date* para configurar a aparência das datas:

As seguintes configurações estão disponíveis:
* Cultura
* Fuso Horário
* Formato de Data
Por exemplo, a seguinte configuração resulta na data abaixo:
* Cultura: **Portuguese (Brazil)**
* Fuso: **UTC**
* Formato de data: **Completo com fuso**

Outra configuração possível seria:
* Cultura: **Portuguese (Brazil)**
* Fuso: **(UTC-03:00) Brasilia**
* Formato de data: **Data e hora curtas**

> **Observação**
>
> O horário é ajustado automaticamente de acordo com o horário de verão (no caso dos fusos em que este se aplica).
## Formatos de data
Os formatos de data suportados são:
Formato | Cultura = *Portuguese (Brazil)* | Cultura = *English (United States)*
----------------------- | ------------------------------------------ | -------------------------------------
Completo com fuso | 21/05/2018 19:48:51 -03:00 | 5/21/2018 7:48:51 PM -03:00
Data e hora longas | segunda-feira, 21 de maio de 2018 19:48:51 | Monday, May 21, 2018 7:48:51 PM
Data e hora curtas | 21/05/2018 19:48 | 5/21/2018 7:48 PM
Data longa e hora curta | segunda-feira, 21 de maio de 2018 19:48 | Monday, May 21, 2018 7:48 PM
Data curta e hora longa | 21/05/2018 19:48:51 | 5/21/2018 7:48:51 PM
# [Tags da representação visual de assinaturas PAdES](https://docs.lacunasoftware.com/articles/rest-pki/pades-tags)
As seguintes tags são suportadas:
Tag | Valor
----------------- | -----------
`{{name}}` | Valor mais apropriado para ser utilizado como nome do assinante (mais recomendado para esse propósito do que o `{{subject_cn}}`)
`{{national_id}}` | Valor mais apropriado para ser utilizado como número de identidade nacional do assinante. Para certificados ICP-Brasil, corresponde ao CPF do assinante. Para certificados Italianos: *codice fiscale*; Argentina: CUIT; Ecuador: *Cedula de Identidad*; Paraguai: CI; Peru: DNI
`{{email}}` | Endereço de email do assinante
`{{subject_cn}}` | O campo **Common Name** (CN) do titular (*subject name*) do certificado do signatário
`{{issuer_cn}}` | O campo **Common Name** (CN) do emissor (*issuer name*) do certificado do signatário
## Tags específicas para certificados ICP-Brasil
Tag | Valor
------------------------ | -----------
`{{br_cpf}}` | CPF do titular (para certificados de PJ, CPF do responsável pelo certificado)
`{{br_cpf_formatted}}` | Mesmo que `{{br_cpf}}` porém formatado como **000.000.000-00**
`{{br_cpf_masked}}` | CPF do titular formatado e mascarado. Ex: **•••.000.000-••**
`{{br_cnpj}}` | CNPJ da empresa
`{{br_cnpj_formatted}}` | Mesmo que `{{br_cnpj}}` porém formatado como **00.000.000/0000-00**
`{{br_responsavel}}` | Nome do titular (para certificados de PJ, nome do responsável pelo certificado)
`{{br_company}}` | Nome da empresa
`{{br_oab_numero}}` | Número de Inscrição junto a Seccional da OAB (sem zeros à esquerda)
`{{br_oab_uf}}` | Sigla do Estado da Seccional da OAB
`{{br_rg_numero}}` | Número do RG do titular/responsável (sem zeros à esquerda)
`{{br_rg_numero_masked}}`| Número do RG do titular/responsável (sem zeros à esquerda) com metado dos dígitos mascarados. Ex: ••••0000
`{{br_rg_emissor}}` | Órgão emissor do RG do titular/responsável
`{{br_rg_uf}}` | UF do órgão emissor do RG do titular/responsável
## *Aliases*
As seguintes tags são suportadas para manter a retrocompatibilidade:
Tag | Tag equivalente
---------------------- | ---------------
`{{signerName}}` | `{{name}}`
`{{signerEmail}}` | `{{email}}`
`{{signerNationalId}}` | `{{national_id}}`
`{{issuerCommonName}}` | `{{issuer_cn}}`
`{{br_oab_numbero}}` | `{{br_oab_numero}}`
# [privacy-policy](https://docs.lacunasoftware.com/articles/rest-pki/privacy-policy)
> **Observação**
>
> Revisamos nossas políticas para garantir que estamos em conformidade com o Regulamento Geral de Proteção de Dados da União Europeia
# Rest PKI - Política de Privacidade
* Última alteração: 24 de maio de 2018
* Data efetiva: 25 de maio de 2018
Esta Política de Privacidade se aplica ao site [https://pki.rest](https://pki.rest) (o "Site") pertencente e operado pela Lacuna Software Ltda (coletivamente, "Lacuna", "nós", "nós" ou "nosso"). Esta Política de Privacidade descreve como a Lacuna
coleta, usa, compartilha e protege as informações pessoais que você fornece, bem como as informações sobre os usuários finais de nossos clientes que processamos em nome de nossos clientes. Também descreve suas opções em relação ao uso, acesso, correção e exclusão de suas informações pessoais.
## Como coletamos informações
Coletamos as seguintes informações pessoais:
### Informação que você nos fornece sobre você
Quando você se inscreve nos Serviços, solicitamos informações como
* Informações de contato, tal como nome, endereço de e-mail, endereço para correspondência ou número de telefone;
* Identificadores exclusivos, como nome de usuário, número da conta ou senha;
* Endereço IP.
Se você não fornecer essas informações, não poderemos fornecer os Serviços a você.
### Informação que você fornece sobre seus usuários e contatos
Para que possamos efetivar a prestação de nossos serviços, você nos fornece informações que podem identificar seus usuários finais ou funcionários.
Essas informações incluem:
* Certificado digital;
* Endereço IP;
* Hash de documento.
Você também pode nos fornecer informações pessoais sobre seus funcionários como Nome e endereço de e-mail onde podemos enviar recibo de suas compras. Quando você nos fornece informações pessoais sobre seus funcionários, essas informações serão
usadas apenas pelo motivo específico pelo qual foram fornecidas. Se você acredita que um de seus funcionários nos forneceu suas informações pessoais e gostaria de solicitar que elas sejam removidas de nosso banco de dados, entre em contato conosco pelas informações de contato abaixo.
### Informação coletada automaticamente
Quando você utiliza nossos Serviços, nós utilizamos cookies e tecnologias similares para coletar informações sobre sua localização, e suas atividades em nosso site, informações sobre o seu dispositivo e informações de registro. Isso é descrito
com mais detalhes abaixo. Estes cookies e tecnologias similares coletam endereço de protocolo de Internet (IP) anonimizado, tipo do browser, serviço de provedor de internet (ISP), páginas de saída/referências, os arquivos vistos no seu site (e.g., página HTML, gráficos, etc.), sistema de operação, data/hora, e/ou dados do fluxo de cliques.
Lacuna permite que o Google Analytics e o Snowplow Analytics usem essas tecnologias para analisar tendências, administrar o site, rastrear os movimentos dos usuários pelo site e coletar informações demográficas sobre nossa base de usuários como um todo para fins de análise.
Você pode controlar o uso de cookies no nível do navegador individual, mas se você escolher desabilitar cookies, isto pode limitar seu uso de determinados recursos ou funções em nosso site ou serviço. Nós não podemos usar ou permitir o uso de cookies ou tecnologias similares em conexão com os dados de seus clientes.
*Google Analytics:*
Nos usamos uma tela chamada "Google Analytics" para coletar algumas informações que listamos acima sobre o uso de seus Serviços. Usamos as informações que obtemos do Google Analytics para aprimorar os Serviços e ajudar você a aprimorar seu Aplicativo. Para coletar essas informações, o Google Analytics pode definir cookies no seu navegador ou dispositivo móvel ou ler cookies que já estão lá. O Google Analytics também pode receber informações sobre você de aplicativos que você baixou,
que são parceiros do Google. Não combinamos as informações coletadas pelo uso do Google Analytics com informações de identificação pessoal. A capacidade do Google de usar e compartilhar informações coletadas pelo Google Analytics sobre suas visitas aos Serviços de outro aplicativo que seja parceiro do Google é restrita pelos Termos de Uso do Google Analytics e pela Política de Privacidade do Google. Por favor revise isto e veja [https://policies.google.com/technologies/partner-sites](https://policies.google.com/technologies/partner-sites) para informações sobre como o Google usa as informações fornecidas pelo Google Analytics e como você pode controlar as informações fornecidas para o Google. Para impedir que seus dados sejam usados pelo Google Analytics, você pode fazer o download do complemento do navegador de desativação do Google Analytics para o Google Analytics, que pode ser encontrado aqui.
## Finalidade e base jurídica do processamento
Usamos as informações sobre você para fornecer os serviços e para melhorá-los. Utilizamos informações sobre seus clientes ou usuários finais apenas para fornecer os serviços. Na medida em que a lei de proteção de dados da EU se aplica a esses usos de suas informações, agimos como o "controlador de dados". Na medida em que a lei de proteção de dados da UE se aplica ao processamento de dados de seus clientes ou usuários finais, você atua como "controlador de dados" e nós como "processador de dados".
*Para executar os Serviços:* Usamos suas informações para identificá-lo, processar seu pagamento, fornecer suporte e assistência técnica; e comunicar com você sobre os serviços por e-mail. Por exemplo, entraremos em contato para notificá-lo sobre qualquer problema com os Serviços ou sobre qualquer atividade suspeita em sua conta. A base legal para esses usos na execução do contrato com você.
*Para otimizar ou melhorar os serviços:* Usamos suas informações para autenticar você, evitar fraudes e abusos de nossos serviços e realizar pesquisas. A base legal para esses usos é nosso interesse legítimo na melhoria e otimização de nossas ofertas de serviços e em garantir a segurança de nossos serviços, e aplicamos safeguards adequadas para proteger suas informações (veja [section Security](https://docs.lacunasoftware.com/articles/rest-pki/privacy-policy#security)).
## Como nós compartilhamos sua informação
Compartilhamos informações com os provedores de serviços, conforme exigido por lei, e em conexão com a proteção e aplicação de nossos direitos legais e contratuais.
*Com provedores de serviços:* Compartilhamos suas informações com terceiros que prestam serviços em nosso nome para ajudar em nossas atividades comerciais. Isto inclui: processamento de pagamento, prestação de serviço ao cliente, enviando comunicação de marketing (este não está incluído informações sobre seu cliente), conduzindo pesquisas e análises e fornecendo infraestrutura de computação em nuvem. A base legal para o compartilhamento dessas informações é o nosso interesse legítimo em fornecer nossos Serviços com eficiência e implementamos medidas para proteger suas informações.
*Com autoridades públicas ou autoridades policiais:* Em determinadas situações, a Lacuna pode ser obrigada a divulgar dados pessoais em resposta a solicitações legais de autoridades públicas, inclusive para atender aos requisitos de segurança nacional ou de aplicação da lei. Também podemos divulgar suas informações pessoais conforme exigido por lei, como para cumprir uma intimação ou outro processo legal, quando acreditarmos de boa fé que a divulgação é necessária para proteger nossos direitos, quando acreditarmos que há uma violação de nossos Termos de Serviço. Serviço (consulte os Termos de Serviço Rest PKI, Termos de Serviço Beanstalk), proteja sua segurança ou a segurança de outras pessoas, investigue fraudes ou responda a uma solicitação do governo. Se a Lacuna estiver envolvida em uma fusão, aquisição ou venda de todos ou parte de seus ativos, você será notificado por e-mail e / ou um aviso em destaque em nosso site sobre qualquer alteração de propriedade, uso de suas informações pessoais e escolhas que você possa ter em relação às suas informações pessoais. Não vendemos, alugamos ou compartilhamos informações pessoais com terceiros sem o seu consentimento prévio. A base legal para isso é nosso interesse legítimo em proteger nossos direitos legais e os de terceiros, o cumprimento de obrigações legais e nosso interesse legítimo em cumprir obrigações legais impostas por autoridades legais fora da EU.
*Prevenir fraudes e abusos dos Serviços:* Compartilharemos informações para prevenir ou detectar fraudes ou solucionar problemas técnicos e, se acharmos necessário investigar, impedir ou tomar medidas em relação a situações que envolvam abuso da infraestrutura dos Serviços. A base legal para isso é o nosso interesse legítimo em manter a segurança de nossos Serviços.
Também processamos e compartilhamos informações de maneira agregada e não identificada, onde as informações são compartilhadas como parte de um relatório estatístico e não contêm informações pessoais.
## Segurança
A segurança de suas informações pessoais é importante para nós. Implementamos medidas adequadas para proteger as informações pessoais enviadas a nós, durante a transmissão e depois que elas são recebidas. A Lacuna Software Ltda toma medidas para garantir que todo o código-fonte, arquivos e dados permaneçam privados e confidenciais. Devido à natureza sensível do código-fonte, levamos isso muito a sério e o tornamos nossa principal preocupação para todos os clientes. Restringimos o acesso às informações pessoais aos funcionários, contratados e agentes da Lacuna que precisam conhecer essas informações para operar, desenvolver ou melhorar nosso serviço. Esses indivíduos estão sujeitos a obrigações de confidencialidade e podem estar sujeitos a disciplina, incluindo rescisão e processo criminal, se não cumprirem essas obrigações. Se você tiver alguma dúvida sobre a segurança de suas informações pessoais, entre em contato conosco através das informações de contato abaixo.
## Direitos com relação às suas informações
Mediante solicitação, a Lacuna fornecerá informações sobre a retenção ou não de suas informações pessoais. Você pode acessar, corrigir, obter uma cópia ou solicitar a exclusão de suas informações pessoais fazendo login em sua conta ou entrando em contato conosco através das informações de contato abaixo. Responderemos à sua solicitação dentro de um prazo razoável. Em certas circunstâncias, podemos ser obrigados por lei a reter suas informações pessoais, ou talvez precisemos retê-las para continuar prestando um serviço.
A Lacuna Software Ltda reconhece que você tem o direito de acessar suas informações pessoais. A Lacuna não tem relacionamento direto com seus usuários finais cujos dados pessoais são processados. Um indivíduo que busca acessar ou procura corrigir, alterar ou excluir dados imprecisos deve encaminhar sua consulta ao Cliente da Lacuna Software Ltda (o controlador de dados). Se solicitado remover dados, responderemos em um prazo razoável. Em certas circunstâncias, podemos ser obrigados por lei a reter suas informações pessoais, ou talvez precisemos retê-las para continuar prestando um serviço.
## Suas escolhas com relação à informação
Comunicação promocional por email: Você pode se inscrever para receber email, boletim informativo ou outras comunicações nossas. Se você deseja interromper o recebimento dessas informações, atualize suas preferências de email usando o link "Cancelar inscrição" encontrado nos emails que enviamos para você ou entrando em contato conosco pelas informações de contato abaixo.
Google Analytics: veja [https://policies.google.com/technologies/partner-sites](https://policies.google.com/technologies/partner-sites) para informações sobre como o Google usa as informações fornecidas pelo Google Analytics e como você pode controlar as informações fornecidas pelo Google. Para impedir que seus dados sejam usados pelo Google Analytics, você pode fazer o download do complemento do navegador de desativação do Google Analytics que pode ser encontrado [aqui](https://tools.google.com/dlpage/gaoptout/).
## Retenção de dados
Podemos reter suas informações enquanto sua conta estiver ativa ou conforme necessário para fornecer serviços, cumprir nossas obrigações legais, resolver disputas e fazer cumprir nossos acordos. Em certas circunstâncias, podemos ser obrigados por lei a reter suas informações pessoais, ou talvez precisemos retê-las para continuar prestando um serviço.
Mesmo se você excluir sua conta, lembre-se de que a exclusão por nossos fornecedores terceirizados pode não ser imediata e que as informações excluídas podem persistir em cópias de segurança por um período de tempo razoável.
## Política em relação às crianças
Os Serviços não são direcionados a crianças menores de 16 anos. Não coletamos intencionalmente Informações Pessoais de crianças menores de 16 anos. Se você tem menos de 16 anos, não use os Serviços e não forneça nenhuma informação pessoal para nós. Se você perceber que uma criança menor de 16 anos nos forneceu informações pessoais sem o seu consentimento, entre em contato conosco em [suporte@lacunasoftware.com](mailto: suporte@lacunasoftware.com). Se você é residente na Califórnia com menos de 18 anos e deseja remover o conteúdo disponível publicamente, entre em contato conosco em [suporte@lacunasoftware.com](mailto: suporte@lacunasoftware.com) com a linha de assunto "Borracha da Califórnia".
## Transferências de informações que não são do EEE
Na medida em que informações pessoais são transferidas para os Estados Unidos de outros países da Área Econômica Européia e da Suíça, incluindo informações pessoais que são transferidas do Canadá, empregamos safeguard semelhantes, como selecionar cuidadosamente fornecedores de terceiros, garantindo que as informações pessoais usadas e divulgado apenas conforme estabelecido nesta política de privacidade e protegido por safeguard de segurança apropriadas.
No entanto, em conexão com essas transferências de dados, suas informações pessoais podem estar sujeitas a leis de privacidade que podem não fornecer a mesma proteção que seu país de residência. Por exemplo, entidades governamentais em outros países podem ter certos direitos para acessar suas informações pessoais. Ao usar este plugin ou nosso Serviço relacionado a este aviso de privacidade, você concorda com a transferência de suas informações pessoais.
## Navegador Do Not Track
Os Serviços honram e aplicam as preferências Do Not Track. Do Not Track (DNT) é uma preferência de privacidade que você pode definir no seu navegador da web para indicar que não deseja que determinadas informações sobre as visitas às suas páginas sejam coletadas nos sites quando você não interagiu com esse serviço na página. Para todos os detalhes, incluindo como ativar o Do Not Track, visite [donottrack.us] (http://donottrack.us/).
## Mudanças para esta Política de Privacidade
Podemos atualizar esta Política de Privacidade para refletir alterações em nossas práticas de informações. Se fizermos alguma alteração relevante, notificaremos você por e-mail (enviado para o endereço de e-mail especificado em sua conta) ou por meio de um aviso neste site antes da efetivação da alteração. Recomendamos que você reveja periodicamente esta página para obter as informações mais recentes sobre nossas práticas de privacidade.
## Perguntas / Comentários sobre privacidade
Se você tiver questões ou sugestões sobre a Política de Privacidade da Lacuna, por favor entre em contato conosco em [suporte@lacunasoftware.com](mailto:suporte@lacunasoftware.com) ou EQN 102/103, Ed. Avenida 102, 2º Andar - Asa Norte, Brasília-DF, Brasil. Telefone: +55 61 3030-5700.
# [Solicitando carimbos de tempo ao Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/requesting-timestamps)
Você pode utilizar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) como provedor de carimbos de tempo ICP-Brasil fornecidos através
do protocolo TSP (*Timestamp Protocol* - RFC 3161) ou API REST.
Para isso, siga os passos abaixo para obter as credenciais de acesso:
1. Autentique-se no site do Rest PKI (https://pki.rest/)
1. No Painel de Controle, clique no botão **Gerar novo token de acesso**
1. Copie o valor gerado
Em seguida, utilize uma das formas abaixo para comunicação com o Rest PKI.
## Comunicação via *Timestamp Protocol*
Utilize seguintes parâmetros para comunicação com o Rest PKI via TSP:
* Protocolo: HTTPS
* Method: POST
* Url: `https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310`
* Autenticação: por header HTTP conforme abaixo:
* `Authorization` : `Bearer `
## Comunicação via API REST
Utilize seguintes parâmetros para comunicação com o Rest PKI via API REST:
* Url: `https://pki.rest/Api/TimestampPlans/a402df41-8559-47b2-a05c-be555bf66310/Issue`
* Method: POST
* Request headers
* `Authorization` : `Bearer `
* `Content-Type` : `application/json`
* Request
* `algorithm` : nome do algoritmo (veja algoritmos suportados abaixo)
* `value` : valor do hash a ser carimbado, codificado em Base64 **ou**
* `hexValue` : valor do hash a ser carimbado, codificado em hexadecimal (*case insensitive*)
* Response
* `encodedValue` : carimbo de tempo, codificado em Base64
* `info` : informações sobre o carimbo de tempo
Exemplo de request com algoritmo SHA-256 e hash em Base64:
```json
{
"algorithm": "SHA256",
"value": "L80RIvFw2jwpiObzFQN+tuEQQ4E9qnGeYYYd+4DrM8M="
}
```
Exemplo de request com algoritmo SHA-1 e hash em hexadecimal:
```json
{
"algorithm": "SHA1",
"hexValue": "3ade2832feafe081f246aa2fb89cb9c7929ac911"
}
```
## Algoritmos suportados
Atualmente, os seguintes algoritmos são suportados:
* SHA-1
* TSP: 1.3.14.3.2.26
* API: "SHA1"
* SHA-256
* TSP: 2.16.840.1.101.3.4.2.1
* API: "SHA256"
# [Códigos de Retorno do REST PKI](https://docs.lacunasoftware.com/articles/rest-pki/return-codes)
O REST PKI retorna os seguintes códigos HTTP:
## 401 - Unauthorized
O token de acesso fornecido não é válido.
## 422 - Unprocessable Entity
As operações do REST PKI apresentam um conjunto de códigos de retorno que podem ser vistos a seguir, bem como o significado de cada código.
| Códigos de retorno | Significado |
|-------------------------------|--------------------------------------------------------------------------------|
| EntityNotFound | Cultura, fuso horário ou política de assinatura invalido ou desconhecido |
| EntityAlreadyExists | Já existe um contexto de segurança ou política com esse nome. Ou já foi registrado o AC intermediário ou raíz confiável |
| EntityUnusable | Política de assinatura ou contexto de segurança inválido ou mal configurado |
| EntityIsReadOnly | Política ou carimbo de tempo fornecido é apenas para leitura (read-only) |
| SecurityContextRequired | Contexto de segurança não fornecido |
| PKCertificateMalformed | Conteúdo do certificado fornecido é malformado |
| ValidationError | No momento da assinatura ou da autenticação, o certificado do assinante não está válido. Para mais informações nesse erro veja a tabela [abaixo](https://docs.lacunasoftware.com/articles/rest-pki/return-codes#val-errors) |
| ClientSignatureNotSet | Quando o processo de autenticação ou assinatura tenta finalizar, mas nenhuma assinatura foi fornecida pelo WebPKI |
| ClientSignatureAlreadySet | Assinatura realizada pelo WebPKI foi fornecida mais de uma vez |
| ToSignHashNotAvailable | Hash a ser assinado está indisponível |
| CertificateMismatch | Conteúdo do certificado difere do que iniciou a assinatura |
| InvalidRequest | Requisição não é válida |
| TimestampingUnavailable | Serviço de carimbo de tempo está indisponível |
| EndUserCertificate | Certificado final não pode ser adicionado como raíz confiável ou AC intermediário |
| CACertificate | Certificados de AC intermediária não podem ser adicionado como raíz confiável |
| SelfSignedCertificate | Certificados auto-assinados não podem ser adicionados como AC intermediária |
| TimestampPlanRequired | Plano de carimbo de tempo não fornecido |
| InvalidPdf | PDF fornecido não é válido |
| InvalidVisualRepresentation | Representação visual fornecida é inválida |
| MissingDataHash | Data hash não fornecido |
| CannotApplyPdfMarks | PDF já está assinado digitalmente e o parâmetro AbortIfSigned está setado como True |
| BadDigest | Digest fornecido não corresponde ao conteúdo recebido |
| InvalidXml | XML fornecido não é válido |
| TransactionLimitAlreadyExists | Limite de transação já definido, para atualizá-lo use request com método PUT |
| InvalidSortField | Parâmetro de ordenação não foi encontrado na consulta ao banco de dados |
| FontSizeZero | Tamanho da fonte deve ser maior que 0 |
| InvalidPage | O número do página da representação visual fornecida é inválido |
| FileDownloadFailed | Download de arquivo falhou |
| InvalidElementType | O tipo do elemento do pdk mark fornecido é desconhecido |
| UnsupportedCMSContentType | O conteúdo CMS fornecido não é suportado |
| UnsupportedDigestAlgorithm | O algoritmo de *digest* fornecido não é suportado |
| UnsupportedSignatureAlgorithm | O algoritmo de assinatura fornecido não é suportado |
| PadesPositionNotFilled | PadesPositionModel deve ter ao menos um dos campos, Auto ou Manual, preenchido |
| InvoiceNotCreated | Fatura pesquisada ainda não foi criada |
| InvalidCades | Arquivo p7s fornecido não é válido |
| InvalidToken | Token de assinatura fornecido não é válido |
### Erros de validação
Erros de validação ocorrem durante assinaturas ou autenticações ou podem ser retornados nos *endpoints* de validação de assinatura.
| Erros de Validação | Significado |
|-----------------------------------------|------------------------------------------------------------------------------------|
| CertificateNotYetValid | Na data de referência, o certificado ainda não é válido |
| CertificateExpired | Na data de referência, o certificado está expirado |
| CertificateRevoked | Na data de referência, o certificado estava revogado |
| CertificateIssuerNotFound | Emissor do certificado não foi encontrado |
| CertificateRevocationStatusUnknown | Estado de revogação do certificado não pode ser verificado. Provavelmente, o REST PKI não conseguiu acessar os certificados CRLs |
| CertificateChainRootNotTrusted | A raiz da cadeia de certificado não é confiável |
| InvalidCertificateSignature | Assinatura não é válida, baseado na chave pública fornecida para o certificado |
| DecodeError | Não foi possível decodificar parte do objeto sendo validado |
| RequiredSignedAttributeMissing | Assinatura não contém todos os atributos assinados que são requeridos |
| ForbiddenSignedAttributePresent | Assinatura contém um ou mais atributos assinados que são proibidos |
| RequiredUnsignedAttributeMissing | Assinatura não contém todos os atributos não assinados que são requeridos |
| ForbiddenUnsignedAttributePresent | Assinatura contém um ou mais atributos não assinados que são proibidos |
| ContentTypeMismatch | Atributo content-type especifica um tipo de conteúdo diferente do CMS |
| MessageDigestMismatch | Atributo message digest especifica um digest diferente do digest do dado assinado |
| SigningCertificateDigestMismatch | Atributo signing certificate especifica um digest diferente do digest do certificado do assinante |
| SignatureAlgorithmValidationFailed | Validação do algoritmo da assinatura falhou |
| RevocationDataIssuedBeforeGracePeriod | Dados de revogação emitidos antes do fim do período de carência |
| UncertifiedDateReference | A referência de tempo utilizada na validação da assinatura não é certificada |
| SignaturePolicyMismatch | Na validação a política de assinatura difere da política fornecida |
| SigningTimeOutOfCertificateValidity | Assinatura realizada fora do período de validade do certificado |
| UnknownSignedAttributesPresent | Assinatura contém um ou mais atributos assinados não reconhecidos |
| UnknownUnsignedAttributesPresent | Assinatura contém um ou mais atributos não assinados não reconhecidos |
| TimestampWithMoreThanOneSigner | Carimbo de tempo contém mais de um assinante |
| TimestampMessageImprintMismatch | Message-imprint do timestamp não é igual a computada |
| CompleteReferencesMismatch | Referências completas de certificados não são válidas |
| InvalidSignatureTimestamp | Carimbo de tempo da assinatura não é válido |
| InvalidReferencesTimestamp | Nenhum carimbo de tempo de referenência válido foi encontrado |
| InvalidArchiveTimestamp | Carimbo de tempo do arquivo não é válido |
| InvalidKeyUsage | Certificado assinante não tem permissão para realizar esse tipo de operação |
| InvalidOcspResponse | Resposta OCSP não é válida |
| UnauthorizedIssuer | Emissor do certificado não é uma AC |
| UnknownRootTrustStatus | Estado de confiança da raiz não conhecido |
| InvalidTsl | TSL não é válido |
| InvalidCrl | Certificado LCR não é válido |
| CertificateIssuerValid | Emissor do certificado é confiável |
| CertificateIssuerInvalid | Emissor do certificado não é confiável |
| CertificateValidationFailed | Validação do certificado falhou |
| SignatureVulnerableToSignerSubstitution | Assinatura é vulnerável a ataques de substituição de signatário |
| InvalidXmlSignatureSchema | Validação do schema da assinatura (XSD)falhou |
| XmlDSigCoreValidationFailed | Validação primária (core validation) da assinatura falhou (o digest ou assinatura estão incorretos) |
| SignatureTimestampIgnored | Um carimbo de tempo de assinatura foi ignorado |
| InvalidCertificationPathLen | Caminho de certificação das ACs é inválido |
| SigningCertificateNotFound | Certificado do signatário não foi encontrado |
| UnauthorizedACIssuer | Emissor do certificado é uma AC, portanto não pode ser usado como emissor de certificados de atributo |
| AlgorithmNotAllowed | Algoritmo não é permitido |
| UnacceptableSignaturePolicy | Política de assinatura não permitida |
## 500 - Internal Server Error
Ocorreu um erro inesperado no servidor.
# [Rest PKI - Disponibilidade de exemplos por projeto](https://docs.lacunasoftware.com/articles/rest-pki/samples-availability)
Como temos vários projetos de exemplos e estamos sempre acrescentando novos exemplos de funcionalidades, nem todos os
exemplos estão disponíveis em todos os projetos. A tabela abaixo mostra quais exemplos estão disponíveis em quais projetos.
Também temos alguns projetos de exemplos legados, que não são mais atualizados, em [PHP 5.3/5.4](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy), [PHP 5.2](https://docs.lacunasoftware.com/articles/rest-pki/php/legacy52) e [Node.js SPA](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/spa).
| | [PHP 5.5+](https://docs.lacunasoftware.com/articles/rest-pki/php/current) | [ASP.NET MVC](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc) | [ASP.NET Web Forms](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms) | [ASP.NET Core](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore) | [Python Flask](https://docs.lacunasoftware.com/articles/rest-pki/python/flask) | [Java 7+ Spring MVC](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc) | [Java 6 Spring MVC](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6) | [Node.js MVC](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc) | [Ruby on Rails](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails) |
| ---- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **Autenticação** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#auth) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#auth) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#auth) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#auth) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#auth) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#auth) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#auth) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#auth) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#auth) |
| **PAdES** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#pades) |
| **PAdES com upload** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#pades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#pades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#pades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#pades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#pades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#pades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#pades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#pades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#pades-upload) |
| **Assinatura conjunta PAdES** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#pades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#pades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#pades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#pades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#pades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#pades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#pades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#pades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#pades-cosign) |
| **Marcas PAdES** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#pdf-marks) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#pdf-marks) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#pdf-marks) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#pdf-marks) | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#pdf-marks) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#pdf-marks) | | |
| **PAdES com chave do servidor** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#pades-server) | ✗ | | Em teste | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#pades-server) | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#pades-server) | |
| **PAdES sem comunicação com o cliente** | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#pades-wo-client) | | | | | | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#pades-wo-client) |
| **Abrir PAdES** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#open-pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#open-pades) | | Preview | Dev | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#open-pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#open-pades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#open-pades) | |
| **Versão para impressão** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#print) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#print) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#print) | | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#print) | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#print) | |
| **CAdES** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#cades) |
| **CAdES com upload** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#cades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#cades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#cades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#cades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#cades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#cades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#cades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#cades-upload) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#cades-upload) |
| **Assinatura conjunta CAdES** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#cades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#cades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#cades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#cades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#cades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#cades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#cades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#cades-cosign) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#cades-cosign) |
| **CAdES com chave do servidor** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#cades-server) | ✗ | | Em teste | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#cades-server) | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#cades-server) | |
| **Abrir CAdES** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#open-cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#open-cades) | | Preview | Dev | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#open-cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#open-cades) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#open-cades) | |
| **Elemento XML** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#xml-element) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#xml-element) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#xml-element) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/netcore#xml-element) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#xml-element) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#xml-element) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#xml-element) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#xml-element) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#xml-element) |
| **XML completo** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#xml-full) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#xml-full) | | Preview | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#xml-full) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#xml-full) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#xml-full) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/nodejs/mvc#xml-full) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#xml-full) |
| **Abrir XML** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#open-xml) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#open-xml) | | Preview | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#open-xml) | | | |
| **Lote (PAdES)** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#batch) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#batch) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#batch) | Preview | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#batch) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc#batch) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/java/mvc-java6#batch) | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#batch) |
| **Lote otimizado (PAdES)**| Dev | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#batch-optimized) | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/web-forms#batch-optimized) | | | | | | |
| **Lote (CAdES)** | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#batch-cades) | | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/python/flask#batch-cades) | | | | [✔](https://docs.lacunasoftware.com/articles/rest-pki/ruby/rails#batch-cades) |
| **Lote (elemento XML)** | [✔](https://docs.lacunasoftware.com/articles/rest-pki/php/current#batch-xml-element) | | | | | | | Em teste | Em teste |
> **Dica**
>
> Alguns dos símbolos `✓` acima são links, que levam a uma descrição dos arquivos relevantes do exemplo
> correspondente no respectivo projeto de exemplos.
## Legenda
| Símbolo | Descrição |
| ------- | -------------- |
| ✓ | Disponível |
| | Não disponível (em breve) |
| Dev | Em desenvolvimento |
| Preview | Implementado, mas pendente de revisão; pode ser visualizado em branch separado |
| ✗ | Não planejado (não será implementado) |
# [Validar assinaturas - Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/validate-signatures)
> **Aviso: Produto legado**
>
> O Rest PKI é um produto legado: continua recebendo correções de bugs e de segurança, mas novas integrações em nuvem
> devem usar o [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core), que também atende à maioria das APIs do Rest PKI. Esta página é para
> integrações existentes com o Rest PKI. Veja a [referência rápida do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference).
O seu backend envia um arquivo assinado ao [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki), que o abre, lista os signatários e valida cada assinatura. Não há etapa no
navegador. O Rest PKI abre assinaturas em PDF (PAdES), arquivos CMS/CAdES (`.p7s`) e assinaturas XML.
O código desta página é C#, do [projeto de exemplos em ASP.NET MVC](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc), que usa o `Lacuna.RestPki.Client` 1.24.1.
Os mesmos samples existem em outras linguagens: veja a [disponibilidade de exemplos por projeto](https://docs.lacunasoftware.com/articles/rest-pki/samples-availability).
## Fluxo
1. Crie o `RestPkiClient` com o seu endpoint e o seu token de acesso (veja a [referência rápida](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference)).
1. Crie um `PadesSignatureExplorer`, `CadesSignatureExplorer` ou `XmlSignatureExplorer` e defina `Validate = true`, a política de
assinatura e o contexto de segurança.
1. Passe o arquivo assinado com `SetSignatureFile()`.
1. Chame `OpenAsync()` (no CAdES, `OpenAndExtractContentAsync()` para obter também o conteúdo assinado).
1. Leia o certificado, a data da assinatura e o `ValidationResults` de cada signatário.
| Explorador | Endpoint | Resposta |
|---|---|---|
| `PadesSignatureExplorer` | `POST Api/PadesSignatures/Open` | `PadesSignatureModel` |
| `CadesSignatureExplorer` | `POST Api/CadesSignatures/Open` | `CadesSignatureModel` |
| `XmlSignatureExplorer` | `POST Api/XmlSignatures/Open` | uma lista de `XmlSignatureModel` |
## Assinaturas em PDF (PAdES)
O sample aceita qualquer assinatura PAdES (`StandardPadesSignaturePolicies.Basic`) cujo certificado o contexto de segurança considere
confiável:
```cs
var sigExplorer = new PadesSignatureExplorer(Util.GetRestPkiClient()) {
Validate = true,
DefaultSignaturePolicyId = StandardPadesSignaturePolicies.Basic,
SecurityContextId = Util.GetSecurityContextId()
};
sigExplorer.SetSignatureFile(userfilePath);
var signature = await sigExplorer.OpenAsync();
```
## Assinaturas CAdES
O sample de CAdES aceita as políticas CAdES da ICP-Brasil em `SignaturePolicyCatalog.GetPkiBrazilCades()` (AD-RB, AD-RT e AD-RC) e
também obtém o conteúdo assinado quando o arquivo o traz:
```cs
var sigExplorer = new CadesSignatureExplorer(Util.GetRestPkiClient()) {
Validate = true,
AcceptableExplicitPolicies = SignaturePolicyCatalog.GetPkiBrazilCades(),
SecurityContextId = Util.GetSecurityContextId(),
};
sigExplorer.SetSignatureFile(userfilePath);
var data = await sigExplorer.OpenAndExtractContentAsync();
var signature = data.Signature;
var encapsulatedContent = data.Signature.HasEncapsulatedContent ? data.EncapsulatedContent : null;
```
* `OpenAndExtractContentAsync()` define `extractEncapsulatedContent` na requisição. O Rest PKI guarda o conteúdo extraído por uma hora, e
o cliente o devolve como um arquivo que você lê com `GetContent()` ou grava com `WriteToFile()`.
* O Rest PKI não informa o tipo de arquivo do conteúdo: o sample supõe um PDF.
* Para uma assinatura destacada (detached), o cliente também tem `SetDataFile()`.
TODO(docs): nenhum sample mostra como validar uma assinatura CAdES destacada.
## Assinaturas XML
O sample de XML aceita qualquer assinatura XmlDSig (`StandardXmlSignaturePolicies.XmlDSigBasic`) cujo certificado o contexto de
segurança considere confiável. `OpenAsync()` devolve uma lista, com um item por assinatura no documento:
```cs
var sigExplorer = new XmlSignatureExplorer(Util.GetRestPkiClient()) {
Validate = true,
DefaultSignaturePolicyId = StandardXmlSignaturePolicies.XmlDSigBasic,
SecurityContextId = Util.GetSecurityContextId()
};
sigExplorer.SetSignatureFile(userfilePath);
var signatures = await sigExplorer.OpenAsync();
```
Cada assinatura XML também tem um `Type` (`FullXml`, `XmlElement` ou `DetachedResource`) e o `SignedElement`.
## Políticas de assinatura
Cada assinatura é validada segundo uma política de assinatura:
* `DefaultSignaturePolicyId` valida as assinaturas que não têm política explícita e aquelas cuja política explícita não está em
`AcceptableExplicitPolicies`. Para aceitar só uma lista de políticas explícitas, omita-o.
* `AcceptableExplicitPolicies` lista as políticas explícitas a aceitar, como as da ICP-Brasil.
* Uma assinatura sem política explícita, quando não há política padrão, falha na validação.
As políticas padrão ficam em `StandardPadesSignaturePolicies` (`Basic`, `BasicWithoutLtv`, `AdobeReader`), `StandardCadesSignaturePolicies`
(`CadesBes`, `CadesBesWithSigningTimeAndNoCrls`, `CmsPermissive`) e `StandardXmlSignaturePolicies` (`XadesBes`, `XmlDSigBasic`,
`CodSha1`, `CodSha256`), cada uma com as políticas da ICP-Brasil aninhadas em `PkiBrazil`; `SignaturePolicyCatalog` agrupa as da
ICP-Brasil.
## Contexto de segurança
O contexto de segurança decide quais certificados raiz são confiáveis.
As políticas PAdES `Basic`, CAdES `CadesBes` e XML `XmlDSigBasic` e `XadesBes` exigem um, ou a chamada falha com `SecurityContextRequired`; as políticas da ICP-Brasil trazem o próprio contexto, que um contexto passado por você
substitui.
| Contexto de segurança | Confia em | Id |
|---|---|---|
| `StandardSecurityContexts.PkiBrazil` | Raízes da ICP-Brasil | `201856ce-273c-4058-a872-8937bd547d36` |
| `StandardSecurityContexts.PkiItaly` | Raízes da ICP italiana | `c438b17e-4862-446b-86ad-6f85734f0bfe` |
| `StandardSecurityContexts.WindowsServer` | Raízes confiáveis do Windows | `3881384c-a54d-45c5-bbe9-976b674f5ec7` |
| `StandardSecurityContexts.LacunaTest` | Raízes da ICP-Brasil, do Windows e de teste da Lacuna; só para desenvolvimento | `803517ad-3bbc-4169-b085-60053a8f6dbf` |
Os samples usam `LacunaTest` nos builds de debug, para aceitar os [certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs), e `PkiBrazil` nos
demais. Em uma instância on-premises, pode ser preciso criar o `LacunaTest` antes (veja [Habilitando o contexto de segurança de testes da Lacuna](https://docs.lacunasoftware.com/articles/rest-pki/on-premises/test-security)).
```cs
public static Guid GetSecurityContextId() {
#if DEBUG
return StandardSecurityContexts.LacunaTest;
#else
return Lacuna.RestPki.Api.StandardSecurityContexts.PkiBrazil;
#endif
}
```
Duas outras opções relaxam a validação: `IgnoreRevocationStatusUnknown` ignora erros por status de revogação desconhecido (padrão `false`,
desde o Rest PKI 1.16.0), e `TrustUncertifiedSigningTime` usa a data da assinatura informada pelo signatário como referência de data da
validação (desde a 1.22.0). Nenhum dos samples as define.
## Ler os resultados
* Uma assinatura PAdES ou CAdES tem `Signers`; cada signatário tem o seu `Certificate`, `SigningTime`, `MessageDigest`, `SignaturePolicy`,
`Timestamps` e `ValidationResults`. Um signatário PAdES também tem `IsDocumentTimestamp` e `SignatureFieldName`. Uma assinatura XML tem
os mesmos campos, sem `MessageDigest`.
* `ValidationResults` tem `IsValid`, `Errors`, `Warnings`, `PassedChecks` e `Summary`, e `ToString()` lista todas as verificações. Ele só
é preenchido quando `Validate` é `true`.
A página do sample mostra, para cada signatário, o nome comum do titular, um selo "valid" ou "invalid" a partir de
`ValidationResults.IsValid`, a data da assinatura, o resumo da mensagem (message digest), a política, os campos ICP-Brasil do certificado
e o `ValidationResults.ToString()` completo.
## Erros
* Um arquivo que não é um PDF, CAdES ou XML válido falha com `InvalidPdf`, `InvalidCades` ou `InvalidXml` (HTTP 422). Veja os
[códigos de retorno](https://docs.lacunasoftware.com/articles/rest-pki/return-codes).
* Quando um limite da conta está definido e é excedido, o Rest PKI responde HTTP 429 com os headers `X-RateLimit-Limit` e
`X-RateLimit-Remaining`.
* TODO(docs): como é informada uma assinatura sem política aceitável.
## Veja também
* [Autenticação com certificado](https://docs.lacunasoftware.com/articles/rest-pki/certificate-authentication)
* Projeto de exemplos em ASP.NET MVC: [abrir PAdES](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#open-pades), [abrir CAdES](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#open-cades), [abrir XML](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/mvc#open-xml)
* [Referência rápida do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference)
**Demonstrações e exemplos**
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Validate a signed PDF using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenPadesSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenPadesRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/open-pades-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-pades-rest/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/open_pades_rest.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/open_pades_rest_controller.rb)
- Validate a CMS/CAdES signature using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenCadesSignatureRestController.cs) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/open-cades-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-cades-rest/index.php)
- Validate a signed XML using Rest PKI: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenXmlSignatureRestController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenXmlRestController.java) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-xml-rest/index.php)
# [Referência rápida do Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/quick-reference)
O essencial do Rest PKI Core em uma página, com links para os artigos completos.
## Para que serve
O Rest PKI Core acrescenta assinaturas digitais, validação de assinaturas, carimbos de tempo e verificações biométricas de identidade às suas aplicações por meio de uma API REST, como serviço em core.pki.rest ou numa instância própria (Windows, Linux, Docker ou Azure).
Quando uma etapa depende do usuário, como assinar com um certificado ou tirar uma selfie, o backend cria uma sessão, envia o usuário a uma página hospedada pelo Rest PKI Core e obtém os resultados pela API.
| Alternativa | Quando ela se encaixa melhor |
| --- | --- |
| [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference) (legado) | A sua aplicação já está integrada ao Rest PKI em pki.rest. O Rest PKI Core também atende à maioria das APIs do Rest PKI, o que facilita a migração. |
| [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/quick-reference) | A sua aplicação é .NET e deve assinar e validar com uma biblioteca no próprio processo; o guia de PKI a recomenda para assinaturas no servidor em .NET. |
| [PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference) | Você quer assinar nos seus próprios servidores Windows ou Linux, a partir de praticamente qualquer linguagem, com um arquivo de licença em vez de um serviço de API. |
| [Web PKI](https://docs.lacunasoftware.com/articles/web-pki/quick-reference) | Você precisa apenas da parte do navegador: listar os certificados do usuário e assinar com eles em JavaScript. A página de assinatura hospedada pelo Rest PKI Core já o usa para os certificados locais. |
| [Signer](https://docs.lacunasoftware.com/articles/signer/quick-reference) | Você precisa de um gerenciador de documentos e assinaturas que envie documentos às pessoas e colete as assinaturas delas. |
## Instalação
| Linguagem | Pacote | Major atual | Registry |
| --- | --- | --- | --- |
| .NET | `Lacuna.RestPkiCore.Client` (namespace `Lacuna.RestPki.Client`) | 5 (mais recente: 5.0.0) | https://www.nuget.org/packages/Lacuna.RestPkiCore.Client/ |
| Java | `com.lacunasoftware.restpki:restpkicore-client` (pacote `com.lacunasoftware.restpkicore`) | 1 (mais recente: 1.4.0) | https://central.sonatype.com/artifact/com.lacunasoftware.restpki/restpkicore-client |
| PHP | `lacuna/restpkicore-client` | 1 (mais recente: 1.2.1) | https://packagist.org/packages/lacuna/restpkicore-client |
| Node.js | `restpki-core-client` | 1 (mais recente: 1.0.2) | https://www.npmjs.com/package/restpki-core-client |
| Python | `restpki-ng-python-client` | 0 (mais recente: 0.9.1.1) | https://pypi.org/project/restpki-ng-python-client/ |
| Navegador (widget de biometria) | `lacuna-restpki-widget`, também em cdn.lacunasoftware.com | 1 (mais recente: 1.4.1) | https://www.npmjs.com/package/lacuna-restpki-widget |
| Qualquer outra linguagem (Ruby…) | Nenhum: chame a API REST diretamente | — | — |
No .NET, execute `dotnet add package Lacuna.RestPkiCore.Client`; o pacote é compilado para o .NET Standard 2.0. No Java, não use o `com.lacunasoftware.restpki:restpki-core-client`: é um pacote mais antigo, que não é mais atualizado, embora o número de versão dele (4.0.0) seja maior.
TODO(docs): os exemplos em .NET fixam versões de majors anteriores do `Lacuna.RestPkiCore.Client` (1.10.1 no PkiSuiteSamples dotnet/mvc, 1.3.0 no dotnet/spa, 4.1.0-rc01 no RestBioSamples backend/dotnet), enquanto o NuGet está na 5.0.0; nenhuma página cita o pacote Python `restpki-ng-python-client`, usado pelo exemplo em Python; os Primeiros passos não têm seção de configuração para Node.js nem para Python; a página de sessões de biometria carrega o widget 1.3.1 da CDN, enquanto o npm tem o 1.4.1.
## Configuração e autenticação
* **Chave de API**: envie-a no header `X-Api-Key` de toda requisição. As chaves pertencem a uma aplicação criada no dashboard da instância (**Aplicações**), são exibidas uma única vez e têm o formato `MyApp|0f3c…`. Dê à aplicação o papel **Operador**, o de que a maioria das integrações precisa. Veja [Primeiros passos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#parameters) e [Escolhendo os papéis](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#roles).
* **Endpoints de carimbo de tempo**: recebem a chave como `Authorization: ApiKey SUA_CHAVE_DE_API`, em vez do header `X-Api-Key`. Veja [Solicitando carimbos de tempo](https://docs.lacunasoftware.com/articles/rest-pki/core/requesting-timestamps).
* **Endpoint**: `https://core.pki.rest/` na instância SaaS (se a sua empresa ainda não tem acesso a ela, entre em contato com o [suporte ao desenvolvedor](mailto:suporte@lacunasoftware.com)); numa [instância própria](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises), o endereço do dashboard dela. Acrescente a ele o caminho da API, como em `https://core.pki.rest/api/signature-sessions`. O `api/system/info` não exige chave e retorna a versão da instância.
* **Referência da API**: no caminho `swagger` do endpoint, como em https://core.pki.rest/swagger, e como documento OpenAPI em https://docs.lacunasoftware.com/openapi/rest-pki-core.json.
* **Licenças**: a instância SaaS não usa arquivo de licença, mas uma aplicação que usa o Web PKI com o Rest PKI Core precisa de uma licença do Web PKI; uma instância on-premises precisa das licenças do PKI SDK e do Web PKI. Veja [Licenciamento](https://docs.lacunasoftware.com/articles/welcome/licensing).
No ASP.NET Core, registre os serviços vinculando-os à seção de configuração `RestPki` e depois solicite o `IRestPkiService` (ou o `IRestBioService`, para sessões de biometria) por injeção de dependência. As chaves da seção são `Endpoint`, `ApiKey`, `CultureName` e `TimeoutSeconds` (60 segundos, se não for definido); mantenha a `ApiKey` fora do *appsettings.json*, nos user secrets (`RestPki:ApiKey`) durante o desenvolvimento e na variável de ambiente `RestPki__ApiKey` ou num cofre de segredos em produção. Outras aplicações .NET chamam `RestPkiServiceFactory.GetService(new RestPkiOptions() { ... })` uma vez e reutilizam o serviço.
```cs
builder.Services.AddRestPki().Configure(builder.Configuration.GetSection("RestPki"));
```
TODO(docs): nenhuma página descreve um ambiente de sandbox ou de testes: a documentação cita apenas a instância SaaS em `https://core.pki.rest/` e as instâncias on-premises.
## Operações principais
Os exemplos estão em C#; `restPkiService` e `restBioService` são o `IRestPkiService` e o `IRestBioService` da [Configuração](https://docs.lacunasoftware.com/articles/rest-pki/core/quick-reference#configuração-e-autenticação).
### Criar uma sessão de assinatura
Permite que o usuário assine numa página hospedada pelo Rest PKI Core, fazendo upload dos documentos nela: crie a sessão com uma `returnUrl` e redirecione o usuário para a `RedirectUrl` recebida. `CreateSignatureSessionAsync`, `POST api/signature-sessions`. Veja [Sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions).
```cs
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
ReturnUrl = "http://localhost:8080/SessionCallback",
});
return Redirect(response.RedirectUrl);
```
### Obter os documentos assinados
Quando o usuário volta, a `returnUrl` traz `?signatureSessionId=...`: obtenha a sessão e verifique o status dela, que é `Completed` ou `UserCancelled`. `GetSignatureSessionAsync`, `GET api/signature-sessions/{id}`. A `SignedFile.Url` de cada documento é um link temporário que baixa o arquivo sem a chave de API. Veja [Sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions).
```cs
public async Task Callback(Guid signatureSessionId)
{
var session = await restPkiService.GetSignatureSessionAsync(signatureSessionId);
if (session.Status != SignatureSessionStatus.Completed)
{
return Redirect("/");
}
return View(session);
}
```
### Assinar documentos que a aplicação já tem
Envia os documentos na criação da sessão; o usuário os assina e não pode fazer upload de outros. Referencie cada arquivo por caminho, bytes, stream ou URL com o `FileReference`. Veja [Pré-definindo os documentos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions#predefined-documents) e [Referenciando arquivos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/file-referencing).
```cs
string pdfPath = ...;
var documents = new List {
new SignatureSessionDocumentToSign {
File = FileReference.FromFile(pdfPath),
},
};
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
...
}, documents: documents);
return Redirect(response.RedirectUrl);
```
### Receber o webhook
O Rest PKI Core envia um `POST` HTTP com um `WebhookEventModel` do tipo `DocumentSignatureCompleted` quando um documento é assinado e processado, opcionalmente com autenticação Basic, Bearer ou por chave de API. No [fluxo com webhook](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions#webhook-flow), crie a sessão sem `ReturnUrl` e abra a página de assinatura numa nova aba. Veja [Webhooks](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/webhooks).
```cs
[HttpPost]
public async Task Index(WebhookEventModel eventModel)
{
switch (eventModel.Type)
{
case WebhookEventTypes.DocumentSignatureCompleted:
await OnDocumentSignatureCompletedAsync(eventModel.Document);
break;
}
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
private async Task OnDocumentSignatureCompletedAsync(DocumentModel model)
{
var apiDocument = await restPkiService.GetDocumentAsync(model);
// SignedFile.Url is a temporary link that downloads the signed file without the API key.
var content = await restPkiService.GetContentAsync(apiDocument.SignedFile.Url);
}
```
Um usuário com o papel **Administrador** ou **Gerente** define a URL do webhook e a autenticação dele no dashboard, em **Configurações** > **Configuração de webhook**. Com a autenticação Basic, o header é `Authorization: Basic base64(usuário):base64(senha)`, e não a forma da RFC 7617. Responda com um código de status 2xx; a aba **Chamadas recentes** mostra cada notificação e a resposta da sua aplicação. Veja [Configurando os webhooks](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/webhooks#configurando-os-webhooks).
### Assinar com uma chave guardada no servidor
O Rest PKI Core prepara a assinatura e retorna o hash a assinar, o seu servidor o assina e o Rest PKI Core conclui a assinatura. `PrepareSignatureAsync` e `CompleteSignatureAsync`, `POST api/signature` e `POST api/signature/completion`. Na variante com navegador, é o Web PKI que lê o certificado do usuário e assina o hash, na sua própria página. Veja [Assinaturas em duas etapas](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/two-step-signatures).
```cs
var certificate = new X509Certificate2(certificatePath, "1234");
var certificateReference = CertificateReference.FromX509Certificate2(certificate);
var fileReference = FileReference.FromFile(userfilePath, userfile);
var signatureOptions = new PrepareSignatureOptions() { SecurityContextId = Util.GetSecurityContextId() };
var prepareSignatureResult = await restPkiService.PrepareSignatureAsync(fileReference, certificateReference, signatureOptions);
var toSignHash = prepareSignatureResult.ToSignHash.Value;
var signature = certificate.GetRSAPrivateKey().SignHash(toSignHash, prepareSignatureResult.ToSignHash.Algorithm.HashAlgorithmName, RSASignaturePadding.Pkcs1);
var signedDocument = await restPkiService.CompleteSignatureAsync(prepareSignatureResult.State, signature);
```
### Fazer uma prova de vida
Verifica se há uma pessoa real diante da câmera. Inicie a sessão com um `TrustedOrigin` (widget na sua página) ou uma `ReturnUrl` (redirecionamento), repasse a `SessionUrl` ao frontend e conclua a sessão com o ticket que o usuário traz de volta. `StartLivenessSessionAsync` e `CompleteLivenessSessionAsync`, `POST api/bio/sessions/liveness` e `POST api/bio/sessions/liveness/completion`. Veja [Prova de vida](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/liveness) e [Sessões de biometria](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions).
```cs
var response = await restBioService.StartLivenessSessionAsync(new() {
TrustedOrigin = exampleConfig.Value.TrustedOrigin,
SubjectIdentifier = "Example",
});
_ = response.SessionUrl; // The URL to be loaded in the Widget to start the biometric session.
var result = await restBioService.CompleteLivenessSessionAsync(request.Ticket);
var success = result.Success;
```
### Solicitar um carimbo de tempo
Usa o Rest PKI Core como provedor de carimbos de tempo RFC 3161: envie o hash por `POST` para `api/tsp` no endpoint, ou use o protocolo TSP em `tsp`, com o header `Authorization: ApiKey SUA_CHAVE_DE_API`. Acrescente o plano como sufixo (`api/tsp/PkiBrazil`): na instância SaaS ele é obrigatório, `PkiBrazil` ou `LacunaTest`. A resposta traz `encodedValue`, o carimbo de tempo em Base64. Veja [Solicitando carimbos de tempo](https://docs.lacunasoftware.com/articles/rest-pki/core/requesting-timestamps).
```json
{
"algorithm": "SHA256",
"value": "L80RIvFw2jwpiObzFQN+tuEQQ4E9qnGeYYYd+4DrM8M="
}
```
## Erros
A API responde com códigos de status HTTP. No 422, o corpo é um `ErrorModel` (chamado `ErrorModelV2` na referência da API) cujo `code` é o que o seu tratamento de erros deve testar; a `message` dele está em inglês e serve para os logs. O cliente .NET lança as exceções abaixo, que derivam todas de `Lacuna.RestClient.RestException`. Veja [Códigos de status HTTP](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#http-status-codes) e [Erros no .NET](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#dotnet-errors).
| Status | Exceção no .NET | O que verificar |
| --- | --- | --- |
| 401 | `RestErrorException` (`StatusCode`) | A chave de API está ausente, errada ou expirou; copie a chave inteira, inclusive o nome da aplicação antes do separador |
| 403 | `RestForbiddenOperationException` | A aplicação não tem um papel que permita a operação, normalmente **Operador** |
| 422 | `RestPkiExceptionV2` (`ErrorCode`) | A API rejeitou a requisição: veja o código de erro |
| — | `ValidationException` (`ValidationResults`) | Um certificado ou uma assinatura não passou na validação |
| — | `RestUnreachableException` | Não foi possível acessar o endpoint |
Os primeiros códigos que costumam aparecer são `SignatureSessionNotFound` (verifique o ID da sessão), `BadSignatureSessionOperation` (a operação não é válida para o status da sessão ou do documento), `BackgroundProcessing` (o recurso ainda está sendo processado) e `DocumentNotFound`. A lista em [Códigos de erro](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/error-codes) não é completa.
## Certificados e credenciais de teste
* **Certificados de teste**: baixe os certificados da ICP de testes da Lacuna em [Certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs) e nunca confie nessa ICP em produção.
* **Confiar na ICP de testes**: passe o contexto de segurança padrão `StandardSecurityContexts.LacunaTest` (`803517ad-3bbc-4169-b085-60053a8f6dbf`), que confia na ICP-Brasil e na ICP de testes da Lacuna, como o `SecurityContextId` de uma sessão de assinatura ou do `PrepareSignatureOptions`, ou escolha-o como **Contexto de segurança padrão** da organização em **Configurações** > **Configurações gerais**, no dashboard. O Rest PKI Core cria esse contexto em toda instância ao iniciar. O exemplo em .NET o passa apenas em builds de debug. Veja [Rest PKI Core](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#rest-pki-core) na página de certificados de teste.
* **Carimbos de tempo de teste**: na instância SaaS, use o plano `LacunaTest`.
* **Chave de API dos exemplos**: os projetos do PkiSuiteSamples vêm com uma chave de avaliação para a instância SaaS, que expira em 31/10/2026. Para ter uma chave própria, entre em contato com o [suporte ao desenvolvedor](mailto:suporte@lacunasoftware.com).
TODO(docs): nenhuma página diz se o core.pki.rest oferece chaves de API de teste ou uma organização de avaliação.
## Experimente
Abra as demonstrações ou o projeto de exemplo da sua linguagem:
**Demonstrações e exemplos**
- Demonstração: [Sign a file with Rest PKI Core](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file-rest-pki-core)
- Demonstração: [Timestamp a PDF](https://try.lacunasoftware.com/pt/pki-suite/timestamp-a-pdf)
- Demonstração: [Run a liveness check](https://try.lacunasoftware.com/pt/lacuna-bio/run-a-liveness-check)
- Demonstração: [Enroll and authenticate your face](https://try.lacunasoftware.com/pt/lacuna-bio/enroll-and-authenticate-your-face)
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Sign a PDF with Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureCoreController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-core.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-rest-core/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_signature_rest_core.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_signature_rest_core_controller.rb)
- Sign multiple PDFs with Rest PKI Core: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/batch-pades-signature-core.js)
- Sign a PDF with a server key using Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesServerKeyRestCoreController.cs)
- Signature session with Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/SignatureSessionRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/SignatureSessionRestCoreController.java) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/signature-session-rest-core/index.php)
- Signature session with predefined documents using Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/SignatureSessionPreDefRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/SignatureSessionPreDefRestCoreController.java)
- Validate files uploaded to a Rest PKI Core signature session: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/ValidateFileRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ValidateFileRestCoreController.java) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/validate-file-rest-core/index.php)
- Receive Rest PKI Core webhooks: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/WebhookRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/WebhookRestCoreController.java) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/webhook-rest-core/index.php)
- Authenticate with a digital certificate using Rest PKI Core: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationRestPkiCoreController.java)
# [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core)
O **Rest PKI Core** leva assinaturas digitais, validação de assinaturas, carimbos de tempo e verificação biométrica de identidade às suas
aplicações por meio de uma API REST. Quando uma etapa depende do usuário, como escolher um certificado e assinar ou tirar uma selfie, a página
fica no próprio Rest PKI Core: a sua aplicação cria uma sessão, envia o usuário para ela e recebe os resultados de volta, sem precisar de
código próprio de assinatura ou de câmera.
Use-o como serviço em [core.pki.rest](https://core.pki.rest/) ou mantenha uma instância própria em Windows, Linux, Docker ou Azure.
## Primeiros passos para integração
A integração tem três etapas:
1. Obtenha o **endpoint** da instância e crie uma **chave de API** no dashboard dela
1. Adicione a biblioteca cliente da sua linguagem ([.NET](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#dotnet), [PHP](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#php) ou
[Java](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#java)) ou [chame a API REST diretamente](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#rest)
1. Crie a sua primeira [sessão de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions)
O artigo [Primeiros passos para integração](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started) explica cada uma delas. Em seguida, veja no
[Guia de Integração](https://docs.lacunasoftware.com/articles/rest-pki/core/integration) a funcionalidade de que você precisa.
## Funcionalidades
### Assinaturas digitais
* **Sessões de assinatura**: a sua aplicação cria uma sessão com os documentos a assinar, ou deixa que o usuário faça upload deles, e
redireciona o usuário para uma página de assinatura hospedada pelo Rest PKI Core. Ao final, a sua aplicação obtém os documentos assinados
pela API e também pode ser avisada por [webhook](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/webhooks). Veja [Sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions).
* **Certificados locais e em nuvem**: os usuários assinam com os certificados instalados no computador, em token criptográfico ou em smart
card, por meio do [Web PKI](https://docs.lacunasoftware.com/articles/web-pki), ou com certificados em nuvem. Uma sessão também pode aceitar apenas certificados em
nuvem, sem passar pelas telas do Web PKI.
* **Requisitos de certificados**: restrinja os certificados que podem assinar numa sessão, por exemplo apenas certificados em dispositivo
criptográfico ou apenas o certificado de um determinado CPF. Veja
[Requisitos de certificados](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/certificate-requirements).
* **Formatos e políticas de assinatura**: PAdES para arquivos PDF, CAdES para qualquer arquivo e XAdES para arquivos XML, incluindo as
políticas de assinatura da ICP-Brasil (AD-RB, AD-RT, AD-RC e AD-RA).
* **Preparação dos documentos**: converta arquivos PDF para PDF/A antes da assinatura
([Conversão para PDF/A](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/pdf-a-conversion)), associe
[metadados](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/document-metadata) próprios aos documentos e
[valide os arquivos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/file-validation) que os usuários enviam antes que sejam assinados.
* **Processamento em background**: devolva o usuário à sua aplicação assim que ele assinar, enquanto os documentos ainda estão sendo
processados. Veja [Processamento em background](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/background-processing).
* **Assinatura no desktop**: o [utilitário cliente do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool) assina documentos pela linha de comando,
para aplicações desktop e scripts.
### Validação e preservação
* **Inspeção de assinaturas**: abra assinaturas PAdES, CAdES e XAdES para ver quem assinou e quando, e valide-as.
* **Códigos de verificação**: os documentos assinados podem trazer um código que qualquer pessoa pode informar na página de validação da
instância para conferir o documento e as assinaturas dele.
* **Carimbos de tempo de arquivamento**: estenda assinaturas existentes com carimbos de tempo de arquivamento (AD-RA), para que continuem
validáveis muito depois de os certificados que as geraram expirarem.
### Carimbo de tempo
Os seus sistemas podem solicitar carimbos de tempo RFC 3161 ao Rest PKI Core, pelo protocolo TSP ou por uma API REST. Uma instância
on-premises encaminha as requisições aos provedores de carimbo de tempo que você configurar, em ordem de prioridade, e distribui a carga entre
provedores de mesma prioridade. Veja [Solicitando carimbos de tempo](https://docs.lacunasoftware.com/articles/rest-pki/core/requesting-timestamps) e
[Configuração de Timestamping](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-timestamp).
### Biometria e identidade
* **Sessões de biometria**: páginas hospedadas pelo Rest PKI Core que capturam o rosto e os documentos de identidade do usuário, abertas por
redirecionamento ou incorporadas à sua página com um widget. Veja [Sessões de biometria](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions).
* **Prova de vida**: verifica se há uma pessoa real diante da câmera. Veja [Prova de vida](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/liveness).
* **Cadastro, autenticação e identificação**: cadastre o rosto de uma pessoa, confirme depois que é a mesma pessoa (1:1) ou descubra a quem
pertence um rosto entre as pessoas cadastradas (1:N). Veja [Cadastro biométrico](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/enrollment),
[Autenticação facial](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/authentication) e [Identificação facial](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/identification).
* **Captura de documento**: lê documentos de identidade e compara o rosto do documento com o rosto do usuário. Veja
[Captura de documento](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/id-capture).
* **Identificação por vídeo**: grava o usuário e o documento de identidade dele, com um relatório em PDF dos resultados.
* **Autenticação com certificado**: os usuários entram na sua aplicação com o próprio certificado digital.
### Compatibilidade com o Rest PKI
O Rest PKI Core também atende à maior parte das APIs do [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) clássico, como as de assinaturas PAdES, CAdES e
XML, de autenticação com certificado e de marcas em PDF, o que facilita a migração de aplicações feitas para o Rest PKI.
## Integrações
* **API REST e bibliotecas cliente**: todas as funcionalidades estão disponíveis por uma API REST, descrita na
[referência da API](https://core.pki.rest/swagger), com bibliotecas cliente para .NET, PHP, Java e Node.js. Veja
[Primeiros passos para integração](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started).
* **Webhooks**: o Rest PKI Core avisa a sua aplicação quando um documento é assinado. Veja [Webhooks](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/webhooks).
* **Login**: os usuários do dashboard entram pelo [GrantID](https://docs.lacunasoftware.com/articles/grant-id), o provedor de identidade OpenID Connect da Lacuna. Veja
[Configuração de OpenID Connect](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-oidc).
* **Armazenamento de arquivos**: os documentos ficam no sistema de arquivos local, no Azure Blob Storage, no Amazon S3 (ou em um serviço
compatível com S3) ou no Google Cloud Storage. Veja [Configuração de Blob Storage](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-blob-storage).
## Implantação
Escolha onde o Rest PKI Core roda:
* **Nuvem (SaaS)**: use a instância da Lacuna em [core.pki.rest](https://core.pki.rest/), sem nada para instalar ou manter.
* **On-premises**: mantenha uma instância própria em [Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/windows-server), [Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux)
(Ubuntu ou Rocky Linux), [Docker](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/docker) ou [Azure App Service](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure), com banco de dados SQL Server
ou PostgreSQL. Uma única instância pode atender a várias [organizações](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/create-sub), cada uma com os próprios usuários,
aplicações e configurações. Comece por [Rest PKI Core on-premises](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises).
## Veja também
* [Guia de Integração](https://docs.lacunasoftware.com/articles/rest-pki/core/integration)
* [Rest PKI Core on-premises](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises)
* [Guia de Operação](https://docs.lacunasoftware.com/articles/rest-pki/core/operation)
* [Histórico de versões](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog)
# [Guia de Integração - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration)
Para integrar a sua aplicação ao [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core), comece pelo artigo [Primeiros passos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started).
Em seguida, veja o guia de integração de uma funcionalidade específica:
* [Sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions)
* [Assinaturas em duas etapas](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/two-step-signatures), na sua própria página com o Web PKI ou com uma chave guardada no servidor
* [Sessões de biometria](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions)
## Veja também
* [Webhooks](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/webhooks)
* [Códigos de erro](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/error-codes)
# [Primeiros passos para integração - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started)
Este artigo leva você do zero até uma primeira chamada bem-sucedida ao [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core). Você vai:
1. [Obter as credenciais](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#parameters): o endpoint da instância e uma chave de API
1. [Configurar a biblioteca cliente](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#calling-the-api) da sua linguagem de programação ou chamar a API REST diretamente
1. [Fazer uma primeira chamada](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#first-call) para verificar se está tudo pronto
## Como a integração funciona
A sua aplicação se comunica com o Rest PKI Core por uma API REST, autenticada por uma chave de API. As chamadas são sempre feitas pelo
**backend** da aplicação: a chave de API dá acesso à sua organização e, por isso, nunca deve chegar a um navegador ou a um aplicativo móvel.
Quando uma operação depende do usuário, como escolher um certificado e assinar um documento ou tirar uma selfie para uma verificação
biométrica, você não precisa construir essas páginas. O backend cria uma **sessão**, o Rest PKI Core retorna uma URL e o usuário é enviado a
uma página hospedada pelo Rest PKI Core. Quando termina, o usuário volta à sua aplicação, que obtém os resultados pela API. Veja
[sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions) e [sessões de biometria](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions).
## Obtenha as credenciais
Você precisa de dois parâmetros:
Parâmetro | Descrição
----------- | ---------
**Endpoint** | Endereço da instância do Rest PKI Core. Na instância SaaS, é `https://core.pki.rest/`. Numa instância própria, é o endereço do dashboard dela, como `https://restpki.suaempresa.com.br/`
**Chave de API** | Chave que identifica a aplicação e autentica as chamadas dela. Tem o formato `MyApp\|0f3c…`: um prefixo tirado do nome da aplicação, um `\|` e 64 caracteres hexadecimais
Se você usa a instância SaaS e a sua empresa ainda não tem acesso a ela, entre em contato com o nosso
[suporte ao desenvolvedor](mailto:suporte@lacunasoftware.com). Se você usa uma [instância on-premises](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises) própria e
ainda não tem uma organização com a qual trabalhar, veja [Criação de organização](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/create-sub).
### Crie uma chave de API
As chaves de API pertencem a **aplicações**. Assim que conseguir entrar no dashboard, crie uma aplicação para o seu sistema e uma chave para
ela:
1. Entre no dashboard da instância
1. No menu lateral, clique em **Aplicações** e depois em **Adicionar**
1. Em **Nova Aplicação**, preencha um **Nome** para a aplicação. Se for pedida uma **Organização**, escolha aquela em que o seu sistema vai
trabalhar
1. Em **Papéis**, marque **Operador** (veja [Escolhendo os papéis](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#roles) abaixo)
1. Clique em **Criar**
1. Na página da aplicação, clique em **Chaves** e depois em **Adicionar**
1. Em **Nova Chave de Aplicação**, preencha uma **Descrição** (por exemplo, o ambiente que vai usar a chave) e escolha uma **Expiração**
(veja [Expiração da chave](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#key-expiration) abaixo); depois clique em **Criar**
1. Copie a chave exibida no diálogo **Chave criada** e guarde-a em um local seguro. **Não será possível vê-la novamente**: se você a perder,
crie uma nova chave
> **Aviso: Mantenha a chave de API em segredo**
>
> Quem tiver a chave pode agir sobre a sua organização com os papéis da aplicação dela. Guarde-a como guardaria uma senha: num cofre de
> segredos como o Azure Key Vault, numa variável de ambiente ou, durante o desenvolvimento, nos segredos do usuário (user secrets) do .NET.
> Nunca faça commit dela no controle de versão e nunca a envie a um navegador ou a um aplicativo móvel.
#### Escolhendo os papéis
Os papéis de uma aplicação definem o que as chaves dela podem fazer. Dê à aplicação apenas os papéis de que ela precisa:
Papel | O que permite
----------------- | -------------
**Operador** | As operações de uma integração: criar e usar sessões de assinatura, assinar documentos, inspecionar assinaturas, autenticar usuários com certificados, emitir carimbos de tempo, executar sessões de biometria, adicionar marcas e carimbos aos documentos e consultar transações. **É o papel de que a maioria das integrações precisa**
**Monitor** | Consultar transações, sessões de biometria, cadastros em lote e faturas, sem executar nenhuma operação
**Gerente** | Tudo o que um Operador faz, além de gerenciar as configurações da organização e os contextos de segurança. Pode ver aplicações e usuários, mas não gerenciá-los
**Administrador** | Tudo, inclusive gerenciar aplicações e usuários
#### Expiração da chave
A **Expiração** de uma chave pode ser **Em 1 ano**, **Em 2 anos** ou **Nunca expira**. Quando uma chave expira, as chamadas feitas com ela
passam a falhar com HTTP 401; por isso, se escolher uma chave que expira, planeje a substituição dela: uma aplicação pode ter mais de uma
chave, o que permite criar a nova chave e passar o sistema a usá-la antes que a antiga expire.
## Escolha como chamar a API
As APIs do Rest PKI Core são REST simples com JSON, mas você não precisa lidar com HTTP diretamente. A Lacuna oferece bibliotecas cliente
que encapsulam a API nas convenções de cada linguagem, junto com projetos de exemplo que as usam:
Linguagem | Pacote | Código-fonte | Exemplos
--------- | ------ | ------------ | --------
.NET | [Lacuna.RestPkiCore.Client](https://www.nuget.org/packages/Lacuna.RestPkiCore.Client/) no NuGet | — | [PkiSuiteSamples/dotnet](https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/dotnet)
PHP | [lacuna/restpkicore-client](https://packagist.org/packages/lacuna/restpkicore-client) no Packagist | [GitHub](https://github.com/LacunaSoftware/RestPkiNGPhpClient) | [PkiSuiteSamples/php](https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/php/plain)
Java | [com.lacunasoftware.restpki:restpkicore-client](https://central.sonatype.com/artifact/com.lacunasoftware.restpki/restpkicore-client) no Maven Central | [GitHub](https://github.com/LacunaSoftware/RestPkiNGJavaClient) | [PkiSuiteSamples/java](https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/java/springmvc)
Node.js | [restpki-core-client](https://www.npmjs.com/package/restpki-core-client) no npm | [GitHub](https://github.com/LacunaSoftware/RestPkiCoreNodeClient) | [PkiSuiteSamples/nodejs](https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/nodejs/expressmvc)
Vá para a seção da sua linguagem:
* [.NET](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#dotnet)
* [PHP](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#php)
* [Java](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#java)
Para qualquer outra linguagem, [chame a API diretamente](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#rest).
### .NET
Adicione ao seu projeto o pacote do NuGet [Lacuna.RestPkiCore.Client](https://www.nuget.org/packages/Lacuna.RestPkiCore.Client/):
```bash
dotnet add package Lacuna.RestPkiCore.Client
```
O pacote é compilado para o .NET Standard 2.0, então roda no .NET 5 ou posterior, no .NET Core 2.0 ou posterior e no .NET Framework 4.6.1
ou posterior (a Microsoft recomenda a 4.7.2 ou posterior para bibliotecas .NET Standard 2.0).
> **Observação**
>
> O pacote se chama `Lacuna.RestPkiCore.Client`, mas as classes dele ficam no namespace `Lacuna.RestPki.Client`.
#### Aplicações ASP.NET Core
Registre os serviços do Rest PKI Core no *Program.cs*, vinculando as opções deles à seção de configuração `RestPki`:
```cs
builder.Services.AddRestPki().Configure(builder.Configuration.GetSection("RestPki"));
```
Em seguida, adicione a seção ao *appsettings.json*, sem a chave de API:
```json
{
"RestPki": {
"Endpoint": "https://core.pki.rest/"
}
}
```
Forneça a chave de API por uma fonte que fique fora do controle de versão. Durante o desenvolvimento, use os
[segredos do usuário](https://learn.microsoft.com/aspnet/core/security/app-secrets):
```bash
dotnet user-secrets set "RestPki:ApiKey" "YOUR_API_KEY"
```
Em produção, defina a variável de ambiente `RestPki__ApiKey` ou use um cofre de segredos como o Azure Key Vault. O ASP.NET Core combina
essas fontes na mesma seção `RestPki`.
Onde a aplicação precisar chamar o Rest PKI Core, peça um `IRestPkiService` por injeção de dependência:
```cs
using Lacuna.RestPki.Client;
public class SignatureController : Controller {
private readonly IRestPkiService restPkiService;
public SignatureController(IRestPkiService restPkiService) {
this.restPkiService = restPkiService;
}
...
}
```
O `AddRestPki()` também registra o `IRestBioService`, o serviço usado nas [sessões de biometria](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions).
> **Dica**
>
> Se preferir definir as opções no código, passe-as ao `AddRestPki()`:
> `builder.Services.AddRestPki(options => { options.Endpoint = ...; options.ApiKey = ...; });`
#### Outras aplicações .NET
Se a aplicação não usa a injeção de dependência do ASP.NET Core, como numa aplicação .NET Framework ou de console, obtenha um
`IRestPkiService` da classe estática `RestPkiServiceFactory`:
```cs
using Lacuna.RestPki.Client;
var restPkiService = RestPkiServiceFactory.GetService(new RestPkiOptions() {
Endpoint = ConfigurationManager.AppSettings["RestPkiEndpoint"],
ApiKey = ConfigurationManager.AppSettings["RestPkiApiKey"],
});
```
Neste exemplo, os parâmetros vêm da seção `appSettings` do arquivo *web.config* ou *app.config*, mas podem vir de onde você preferir. Para
sessões de biometria, use `RestPkiServiceFactory.GetBioService()`, que recebe os mesmos parâmetros.
Cada chamada à `RestPkiServiceFactory` cria um novo cliente HTTP; por isso, crie o serviço uma vez e reutilize-o, por exemplo num campo
estático, em vez de criar um a cada requisição.
#### Opções
O `RestPkiOptions` tem as propriedades a seguir, que também podem ser definidas na seção de configuração `RestPki`:
Propriedade | Descrição
---------------- | ---------
`Endpoint` | Endereço da instância do Rest PKI Core. Se o endereço tiver um caminho, termine-o com uma barra (`https://suaempresa.com.br/restpki/`); caso contrário, o último segmento do caminho é descartado
`ApiKey` | A chave de API da aplicação
`CultureName` | Cultura das respostas da API, como `en-US`, `pt-BR` ou `es-ES` (veja [Cultura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#dotnet-culture) abaixo). Também disponível como `Culture`, do tipo `CultureInfo`
`TimeoutSeconds` | Quanto tempo esperar por cada resposta, em segundos. Se não for definido, o cliente espera 60 segundos. Também disponível como `Timeout`, do tipo `TimeSpan`
#### Cultura
O cliente informa ao Rest PKI Core qual cultura usar em cada chamada e, por padrão, usa a cultura da thread atual
(`Thread.CurrentThread.CurrentUICulture`). Isso funciona bem quando a aplicação define a cultura de cada requisição, como o ASP.NET Core faz
com a localização de requisições (request localization).
Para usar sempre a mesma cultura, independentemente da thread, defina `CultureName`. No ASP.NET Core, adicione a propriedade à seção de
configuração:
```json
{
"RestPki": {
"Endpoint": "https://core.pki.rest/",
"CultureName": "en-US"
}
}
```
Nos demais casos, defina-a no `RestPkiOptions`:
```cs
var restPkiService = RestPkiServiceFactory.GetService(new RestPkiOptions() {
...,
CultureName = "en-US",
});
```
#### Erros
Os métodos do `IRestPkiService` podem lançar as seguintes exceções:
Exceção | Quando
--------------------------------- | ------
`RestPkiExceptionV2` | A API rejeitou a requisição (HTTP 422). Compare a propriedade `ErrorCode`, do tipo `ErrorCodesV2`, com os [códigos de erro](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/error-codes). A propriedade `ErrorMessage` descreve o erro em inglês e serve para os seus logs, não para os seus usuários
`ValidationException` | Um certificado ou uma assinatura não passou na validação. A propriedade `ValidationResults` indica o que falhou
`RestForbiddenOperationException` | A aplicação não tem um papel que permita a operação (HTTP 403, veja [Escolhendo os papéis](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#roles))
`RestErrorException` | Qualquer outro erro HTTP, como 401 por uma chave de API ausente ou inválida. A propriedade `StatusCode` tem o código de status HTTP (veja [Códigos de status HTTP](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#http-status-codes))
`RestUnreachableException` | Não foi possível acessar o endpoint: verifique o endereço do endpoint e a sua rede
`TimeoutException` | A resposta demorou mais que `TimeoutSeconds`
`RestPkiExceptionV2` e `ValidationException` ficam no namespace `Lacuna.RestPki.Client`, e as demais classes `Rest…Exception`, no namespace
`Lacuna.RestClient`. Todas derivam de `Lacuna.RestClient.RestException`; para tratar qualquer erro da API num único lugar, capture
`RestException` (e `TimeoutException`, que é uma exceção padrão do .NET).
### PHP
Adicione ao seu projeto o pacote do Composer [lacuna/restpkicore-client](https://packagist.org/packages/lacuna/restpkicore-client) (se ainda
não tem o Composer, [obtenha-o aqui](https://getcomposer.org/)):
```bash
composer require lacuna/restpkicore-client
```
O pacote requer o PHP 5.5 ou posterior e usa o Guzzle 6 ou 7 para fazer as chamadas HTTP. Ele é de código aberto, hospedado no
[GitHub](https://github.com/LacunaSoftware/RestPkiNGPhpClient): fique à vontade para fazer um fork se precisar personalizá-lo.
Crie um `RestPkiService`, que implementa `RestPkiServiceInterface`, passando a ele um `RestPkiCoreClient` construído com o **endpoint** e a
**chave de API**:
```php
use Lacuna\RestPki\RestPkiOptions;
use Lacuna\RestPki\RestPkiCoreClient;
use Lacuna\RestPki\RestPkiService;
$options = new RestPkiOptions('https://core.pki.rest/', getenv('RESTPKI_API_KEY'));
$client = new RestPkiCoreClient($options);
$service = new RestPkiService($client);
```
Neste exemplo, a chave de API vem da variável de ambiente `RESTPKI_API_KEY`, o que a mantém fora do código.
#### Cultura
Para definir a cultura das respostas da API, passe-a como terceiro parâmetro do `RestPkiOptions`:
```php
$options = new RestPkiOptions('https://core.pki.rest/', getenv('RESTPKI_API_KEY'), 'en-US');
```
#### Erros
Todas as exceções a seguir ficam no namespace `Lacuna\RestPki`:
Exceção | Quando
-------------------------- | ------
`RestPkiException` | A API rejeitou a requisição (HTTP 422). `getErrorCode()` retorna o [código de erro](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/error-codes) e `getDetail()` descreve o erro, para os seus logs
`ValidationException` | Um certificado ou uma assinatura não passou na validação
`RestErrorException` | Qualquer outro erro HTTP. `getStatusCode()` retorna o código de status HTTP (veja [Códigos de status HTTP](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#http-status-codes))
`RestUnreachableException` | Não foi possível acessar o endpoint
#### Exemplos em PHP
No nosso [repositório de exemplos](https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/php/plain/public), as pastas cujo nome
termina em `-rest-core` usam o Rest PKI Core. O endpoint e a chave de API são definidos na entrada `restPkiNg` do
[*config.php*](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/config.php).
### Java
Adicione ao seu projeto o pacote `com.lacunasoftware.restpki:restpkicore-client`, do Maven Central. Ele requer o Java 8 ou posterior.
Com o Gradle, no *build.gradle*:
```groovy
repositories {
mavenCentral()
}
dependencies {
implementation 'com.lacunasoftware.restpki:restpkicore-client:1.4.0'
}
```
Com o Maven, no *pom.xml*:
```xml
com.lacunasoftware.restpki
restpkicore-client
1.4.0
```
Consulte o [Maven Central](https://central.sonatype.com/artifact/com.lacunasoftware.restpki/restpkicore-client) para saber qual é a versão
mais recente.
> **Aviso**
>
> Não confunda com o `com.lacunasoftware.restpki:restpki-core-client`, um pacote mais antigo que não é mais atualizado.
O pacote é de código aberto, hospedado no [GitHub](https://github.com/LacunaSoftware/RestPkiNGJavaClient): fique à vontade para fazer um
fork se precisar personalizá-lo.
Obtenha um `RestPkiService` de `RestPkiServiceFactory.getService()`, passando um `RestPkiOptions` com o **endpoint** e a **chave de API**:
```java
import com.lacunasoftware.restpkicore.RestPkiOptions;
import com.lacunasoftware.restpkicore.RestPkiService;
import com.lacunasoftware.restpkicore.RestPkiServiceFactory;
RestPkiOptions options = new RestPkiOptions();
options.setEndpoint("https://core.pki.rest/");
options.setApiKey(System.getenv("RESTPKI_API_KEY"));
RestPkiService service = RestPkiServiceFactory.getService(options);
```
Neste exemplo, a chave de API vem da variável de ambiente `RESTPKI_API_KEY`, o que a mantém fora do código. Se a aplicação acessa a
internet por um proxy, defina-o com `options.setProxy()`.
#### Cultura
Para definir a cultura das respostas da API, chame `setCulture()`:
```java
options.setCulture("en-US");
```
#### Erros
Os métodos do `RestPkiService` declaram `throws Exception`. Estas são as exceções que eles lançam, todas no pacote
`com.lacunasoftware.restpkicore`:
Exceção | Quando
------------------------------- | ------
`RestPkiException` | A API rejeitou a requisição (HTTP 422). `getErrorCode()` retorna o [código de erro](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/error-codes)
`ValidationException` | Um certificado ou uma assinatura não passou na validação
`RestResourceNotFoundException` | O recurso não foi encontrado (HTTP 404)
`RestErrorException` | Qualquer outro erro HTTP. `getStatusCode()` retorna o código de status HTTP (veja [Códigos de status HTTP](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#http-status-codes))
`RestUnreachableException` | Não foi possível acessar o endpoint
#### Exemplos em Java
No nosso [repositório de exemplos](https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/java/springmvc), os controllers cujo
nome termina em `CoreController`, como `SignatureSessionRestCoreController`, usam o Rest PKI Core. O endpoint e a chave de API são
definidos em `rest-pki-core`, no *application.yml*.
### Chamando a API diretamente
As APIs do Rest PKI Core são RESTful, recebem e retornam JSON e, por isso, são fáceis de chamar em qualquer linguagem de programação
moderna. Se a aplicação usa uma linguagem sem biblioteca cliente, chame-as diretamente:
* **URL**: acrescente o caminho relativo da API ao **endpoint**. Por exemplo, `api/signature-sessions` na instância SaaS é
`https://core.pki.rest/api/signature-sessions`
* **Autenticação**: passe a **chave de API** no header `X-Api-Key` de todas as requisições
* **Corpo**: envie JSON com o header `Content-Type: application/json`
Por exemplo:
```plaintext
POST https://core.pki.rest/api/signature-sessions
X-Api-Key: MyApp|0f3c...
Content-Type: application/json
```
Todas as APIs, com as requisições e respostas, estão descritas na [referência da API](https://core.pki.rest/swagger). Numa instância
própria, a referência fica no caminho `swagger` do endpoint, como `https://restpki.suaempresa.com.br/swagger`.
#### Códigos de status HTTP
A API responde com os seguintes códigos de status HTTP:
Código | Significado
--------------------------- | -----------
200 (OK) | A requisição foi bem-sucedida. A resposta depende da API: veja a [referência da API](https://core.pki.rest/swagger)
400 (Bad Request) | A requisição está malformada; por exemplo, falta um campo obrigatório
401 (Unauthorized) | A chave de API está ausente, é inválida ou expirou
403 (Forbidden) | A chave de API é válida, mas a aplicação dela não tem um papel que permita a operação (veja [Escolhendo os papéis](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#roles))
422 (Unprocessable Entity) | A API rejeitou a requisição, normalmente por causa de um parâmetro que precisa ser corrigido. O corpo é um [ErrorModel](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#error-model)
500 (Internal Server Error) | Ocorreu um erro inesperado. O campo `exceptionCode` do corpo ajuda a diagnosticar o problema: informe-o ao entrar em contato com o suporte
#### ErrorModel
Quando a API responde com 422 (Unprocessable Entity), o corpo é um `ErrorModel`, chamado `ErrorModelV2` na [referência da API](https://core.pki.rest/swagger),
com os seguintes campos:
Campo | Tipo | Descrição
------------------- | -------- | ---------
`code` | `string` | O código de erro (veja os [códigos de erro](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/error-codes)). Escreva o tratamento de erros com base neste campo
`message` | `string` | Descrição do erro, em inglês. Serve para os seus logs, não para os seus usuários, já que não é localizada
`details` | `object` | Informações adicionais sobre o erro, quando houver
`validationResults` | `object` | Quando o código é `ValidationError`, os resultados da validação do certificado ou da assinatura que falhou
Por exemplo:
```json
{
"code": "SignatureSessionNotFound",
"message": "Signature session not found: 00000000-0000-0000-0000-000000000000"
}
```
#### Cultura
A API observa o header de requisição `Accept-Language`, que muda a cultura dos textos que a API retorna. As culturas suportadas são:
* `en-US` (ou apenas `en`)
* `pt-BR` (ou apenas `pt`)
* `es-ES` (ou apenas `es`)
> **Observação**
>
> As mensagens de erro não são afetadas por esse header; por isso, devem ser registradas no log, e não exibidas aos seus usuários. Para
> mostrar um erro a um usuário, associe o `code` dele a uma mensagem própria.
## Faça uma primeira chamada
Com as credenciais e a biblioteca cliente prontas, verifique se tudo funciona.
Primeiro, verifique o **endpoint**: abra o endereço `api/system/info` do endpoint num navegador, como
[https://core.pki.rest/api/system/info](https://core.pki.rest/api/system/info). Esse endereço não exige chave de API e retorna a versão da
instância:
```json
{"productName":"Lacuna Rest PKI Core","productVersion":"5.0.5 RTM","spaVersion":"...","timestamp":"..."}
```
Depois, verifique a **chave de API** criando uma [sessão de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions). Em .NET:
```cs
using Lacuna.RestPki.Api.SignatureSessions;
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
ReturnUrl = "https://localhost:5001/signature-callback",
});
// Send the user to response.RedirectUrl
```
Chamando a API diretamente:
```bash
curl -X POST "https://core.pki.rest/api/signature-sessions" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "returnUrl": "https://localhost:5001/signature-callback" }'
```
A resposta traz o ID da sessão e a URL da página de assinatura dela:
```json
{
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"redirectUrl": "https://core.pki.rest/..."
}
```
Abra a `redirectUrl` num navegador para ver a página em que os seus usuários vão assinar. Se, em vez disso, a chamada falhar:
* **401**: a chave de API não foi enviada ou está errada. Verifique se você copiou a chave inteira, incluindo o nome da aplicação antes do `|`
* **403**: a aplicação não tem o papel **Operador** (veja [Escolhendo os papéis](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#roles))
* **Endpoint inacessível**: verifique o endereço do endpoint e se o seu servidor consegue acessá-lo
## Próximos passos
* [Sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions): permita que os seus usuários assinem documentos numa página hospedada pelo Rest PKI
Core
* [Assinaturas em duas etapas](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/two-step-signatures): assine na sua própria página com o Web PKI, ou com uma chave guardada no servidor
* [Sessões de biometria](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions): prova de vida, cadastro, autenticação e identificação facial e captura de documento
* [Webhooks](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/webhooks): seja avisado quando um documento for assinado
* [Referenciando arquivos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/file-referencing): as formas de enviar documentos à API
* [Utilitário cliente do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool): assine documentos pela linha de comando e a partir de aplicações desktop
* [Solicitando carimbos de tempo](https://docs.lacunasoftware.com/articles/rest-pki/core/requesting-timestamps): use o Rest PKI Core como provedor de carimbos de tempo
* [Códigos de erro](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/error-codes): os códigos que a API pode retornar
**Demonstrações e exemplos**
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Demonstração: [Sign a file with Rest PKI Core](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file-rest-pki-core)
- Authenticate with a digital certificate using Rest PKI Core: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationRestPkiCoreController.java)
- Sign a PDF with Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureCoreController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-core.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-rest-core/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_signature_rest_core.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_signature_rest_core_controller.rb)
- Sign multiple PDFs with Rest PKI Core: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/batch-pades-signature-core.js)
- Sign a PDF with a server key using Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesServerKeyRestCoreController.cs)
# [Utilitário cliente do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool)
O **utilitário cliente do Rest PKI** é um aplicativo de linha de comando para assinar documentos, entre outras operações, em aplicativos desktop utilizando o
[Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core). Por exemplo, para assinar um PDF:
```sh
rpkitool document.pdf --cert-cpf 11111111111 --overwrite
```
O utilitário consiste em um único arquivo executável que pode ser obtido em:
**[Rest PKI Client Tool for Windows](https://cdn.lacunasoftware.com/restpkicore/client-tool/win-x86/rpkitool.exe)**
Para outras plataformas, [clique aqui](https://cdn.lacunasoftware.com/restpkicore/client-tool/).
## Configuração
Para utilizar o utilitário, é preciso configurar o endereço da instância do Rest PKI Core (o *endpoint*) e a chave de API. Esses parâmetros podem ser configurados
por variáveis de ambiente ou por parâmetros para o comando.
Configuração com variáveis de ambiente `RestPki__Endpoint` e `RestPki__ApiKey`:
```plaintext
RestPki__Endpoint=https://restpki.yourcompany.com/
RestPki__ApiKey=YOUR_API_KEY
```
Configuração com parâmetros `--endpoint` e `--api-key`:
```sh
rpkitool ... --endpoint https://restpki.yourcompany.com/ --api-key YOUR_API_KEY
```
> **Observação**
>
> As chaves de API do Rest PKI Core geralmente contém um caractere de barra vertical (`|`), por exemplo `yourapp|12345678` (porém mais longo). Se isso constituir
> um problema para o seu caso, você pode utilizar apenas a parte após a barra vertical (no exemplo anterior, apenas `12345678`).
### Configurações opcionais
O timeout padrão nas chamadas ao Rest PKI Core é de 5 minutos. Pode ser necessário aumentar esse tempo se você estiver tentando assinar arquivos muito grandes
ou em situações de conexão lenta com a internet. Essa configuração pode ser alterada com a variável de ambiente `RestPki__TimeoutSeconds`:
```plaintext
RestPki__TimeoutSeconds=600
```
Ou com o parâmetro `--timeout`:
```sh
rpkitool ... --timeout 600
```
Os exemplos acima configurariam o timeout para 10 minutos (600 segundos).
## Comandos
Cada operação que pode ser realizada com o utilitário é chamada de um **comando**. Os seguintes comandos estão disponíveis:
* [sign](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool/sign) - Assina um documento utilizando um dos certificados do usuário
* [list-certs](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool/list-certs) - Lista os certificados presentes no repositório de certificados do usuário
* [allocate-keys](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool/allocate-keys) - Reserva chaves de validação de documentos
## Saída / códigos de retorno
Se o comando for bem sucedido, o código de retorno será `0` e qualquer saída será escrita para o `stdout`.
Caso ocorra um erro, o código de retorno será `>= 1` e informações sobre o erro serão escritas para o `stderr`. Veja abaixo a lista de códigos de retorno.
Exit code | Description
--------- | -----------
0 | Success
1 | Unspecified error
2 | Bad syntax
3 | Missing API parameters
4 | File not found
5 | Unacceptable filename
6 | File rejected
7 | Certificate not specified
8 | Certificate not found
9 | Certificate rejected
10 | Bad `--count` argument
12 | Rest PKI API exception
## Logs
Por padrão, os arquivos de log são escritos para:
* Em Windows: `%LOCALAPPDATA%\Lacuna Software\Rest PKI Tool\Logs` (ex: *C:\Users\USERNAME\AppData\Local\Lacuna Software\Rest PKI Tool\Logs*)
* Em Linux: `~/.local/share/rpkitool/logs` (ex: */home/USERNAME/.local/share/rpkitool/logs*)
A variável de ambiente `Serilog__WriteToPath` pode ser usada para alterar a pasta na qual os logs são escritos.
Por padrão, apenas logs de severidade `Warning` ou superior são escritos. A variável de ambiente `Serilog__MinimumLevel__Default` pode ser usada para alterar esse
comportamento. Os níveis de severidade, do menos severo para o mais severo, são:
* `Verbose`
* `Debug`
* `Information`
* `Warning`
* `Error`
* `Fatal`
# [Comando do utilitário cliente: **allocate-keys**](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool/allocate-keys)
## Nome
`allocate-keys` - Reserva chaves de validação de documentos
## Sintaxe
```sh
rpkitool allocate-keys []
```
## Exemplos
Para reservar uma única chave:
```sh
rpkitool allocate-keys
```
Para reservar 10 chaves:
```sh
rpkitool allocate-keys 10
```
## Saída
As chaves são escritas no `stdout`, uma por linha, com formatação, por exemplo:
```sh
AAAA-AAAA-AAAA-AAAA
BBBB-BBBB-BBBB-BBBB
CCCC-CCCC-CCCC-CCCC
```
# [Comando do utilitário cliente: **list-certs**](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool/list-certs)
## Nome
`list-certs` - Lista os certificados presentes no repositório de certificados do usuário
## Sintaxe
```sh
rpkitool list-certs [options]
```
Opções:
* `--icpbr`: Listar apenas certificados ICP-Brasil\*
* `--cpf `: Listar apenas certificados com o CPF especificado
\* São listados os certificados que *parecem ser* da ICP-Brasil. A verificação se um determinado certificado é um certificado ICP-Brasil válido é uma tarefa
complexa feita em outro momento pelo Rest PKI, não cabendo ao utilitário fazer esse tipo de crítica.
## Exemplos
Para listar todos os certificados com o CPF `11111111111`:
```sh
rpkitool list-certs --cpf 11111111111
```
## Saída
São escritas para o `stdout` as seguintes informações de cada certificado encontrado, separadas por ponto-e-vírgula (`;`), um certificado por linha:
1. *Thumbprint* do certificado (para ser usado como argumento para outros comandos, por exemplo [sign](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool/sign))
1. Descrição do certificado composta pelo nome do titular e nome da autoridade certificadora (AC)
Exemplo:
```plaintext
78785300913FB340D6ADC12FA2A9F21CF1498703;FULANO DE TAL:11111111111 (AC VALID RFB)
C6BCC85172428B2454F24A588C03E6BD0CB035ED;FULANO DE TAL:11111111111 (AC FENACON RFB)
```
# [Comando do utilitário cliente: **sign**](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool/sign)
## Nome
`sign` - Assina um documento utilizando um dos certificados do usuário
## Sintaxe
```sh
rpkitool sign [options]
```
O comando recebe um único argumento, o caminho do arquivo a ser assinado.
Opções:
* `--cert-cpf `: CPF do certificado a ser utilizado
* `--cert-thumb `: *Thumbprint* do certificado a ser utilizado
* `--output-file `: Caminho do arquivo de saída
* `--document-key `: Chave de validação do documento
* `--overwrite`: Sobrescreve arquivo de entrada
* `--name `: Especifica um nome de arquivo diferente a ser usado nas chamadas à API
O arquivo assinado pode ser escrito em um outro caminho, especificado com o parâmetro `--output-file`, ou pode ser escrito sobre o arquivo de entrada
especificando o parâmetro `--overwrite`.
O certificado a ser utilizado pode ser especificado pelo CPF do usuário com o parâmetro `--cert-cpf` ou pelo *thumbprint* do certificado. Nesse último caso,
a aplicação chamaria primeiro o comando [list-certs](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool/list-certs) para obter a lista dos certificados disponíveis e exibir ao usuário. O *thumbprint* do certificado
escolhido pelo usuário deve então ser especificado com o parâmetro `--cert-thumb`.
## Exemplos
Para assinar o arquivo `document.pdf` com qualquer certificado de CPF `11111111111` que seja encontrado, sobrescrevendo o arquivo de entrada com o arquivo assinado:
```sh
rpkitool sign document.pdf --cert-cpf 11111111111 --overwrite
```
Para assinar o mesmo arquivo porém com um certificado específico, determinado por seu *thumbprint* (escolhido pelo usuário dentre as opções retornadas pelo comando
[list-certs](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/client-tool/list-certs)), escrevendo o documento assinado no arquivo `signed.pdf`:
```sh
rpkitool sign document.pdf --cert-thumb AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA --output-file signed.pdf
```
## Saída
O `documentId` é escrito no `stdout`, que pode ser utilizado posteriormente para referenciar o documento.
# [Sessões de assinatura - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions)
**Sessões de assinatura** são uma funcionalidade do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) que permitem que seus usuários assinem documentos digitalmente sem que você
precise implementar uma página de assinatura digital na sua aplicação.
Para criar uma sessão de assinatura, sua aplicação especifica um `returnUrl` e recebe de volta um `redirectUrl`. Sua aplicação então redireciona o usuário para o
`redirectUrl`, local onde ele irá realizar as assinaturas digitais:

Uma vez que o processo seja finalizado, o usuário será redirecionado de volta à sua aplicação no endereço `returnUrl` especificado durante a criação da sessão. Um
*query parameter* será adicionado à URL contendo o ID da sessão de assinatura (`?signatureSessionId=...`). Usando esse ID, a sua aplicação chama o Rest PKI Core para
obter os documentos assinados pelo usuário:

## Criando a sessão de assinatura
> **Dica**
>
> Desse ponto em diante estaremos descrevendo chamadas ao Rest PKI Core. Para conseguir acompanhar, certifique-se de que já fez os [Primeiros passos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started).
Em .NET, chame `IRestPkiService.CreateSignatureSessionAsync()` e use o `RedirectUrl` retornado para redirecionar o usuário:
```cs
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
ReturnUrl = "http://localhost:8080/SessionCallback",
});
return Redirect(response.RedirectUrl);
```
Veja [exemplo de criação de sessão de assinatura em .NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/SignatureSessionRestCoreController.cs).
Em PHP, chame `RestPkiServiceInterface.createSignatureSession()` e use o campo `$redirectUrl` retornado para redirecionar o usuário:
```php
$request = new CreateSignatureSessionRequest();
$request->returnUrl = "http://localhost:8080/SessionCallback";
$response = $service->createSignatureSession($request);
header("Location: {$response->redirectUrl}");
```
Veja [exemplo de criação de sessão de assinatura em PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/signature-session-rest-core/index.php).
Em Java, chame `RestPkiService.createSignatureSession()` e use o `RedirectUrl` retornado para redirecionar o usuário:
```java
CreateSignatureSessionRequest request = new CreateSignatureSessionRequest();
request.setReturnUrl("http://localhost:60695/signature-session-rest-core/complete");
CreateSignatureSessionResponse response = restPkiService.createSignatureSession(request);
return "redirect:" + response.getRedirectUrl();
```
Veja [exemplo de criação de sessão de assinatura em Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/SignatureSessionRestCoreController.java).
Caso esteja chamando a API diretamente:
```plaintext
POST {endpoint}/api/signature-sessions
{
"returnUrl": "http://localhost:8080/SessionCallback"
}
```
Vamos analisar o `returnUrl` utilizado nos exemplos acima:
```plaintext
http://localhost:8080/SessionCallback
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
Endereço da sua app Rota para o tratamento de retorno
```
Ao adaptar os exemplos para a sua aplicação, você precisa alterar a primeira porção para o endereço da sua própria aplicação e também a rota para o tratamento do
retorno na sua aplicação (veja a seção abaixo).
## Implementando o tratamento de retorno
O `returnUrl` é o endereço para no qual o Rest PKI Core enviará o seu usuário de volta para a sua aplicação uma vez que o processo de assinatura seja concluído.
> **Observação**
>
> Até aqui, estamos usando `/SessionCallback` como URL relativa do tratamento de retorno, porém sinta-se à vontade para usar a URL relativa mais adequada à sua
> aplicação
Em .NET, a implementação do tratamento de retorno seria algo como:
```cs
public class SessionCallbackController : Controller {
...
public async Task Index(Guid signatureSessionId) {
var session = await restPkiService.GetSignatureSessionAsync(signatureSessionId);
if (session.Status != SignatureSessionStatus.Completed) {
return Redirect("/");
}
return View(session);
}
}
```
Veja [exemplo de tratamento de retorno em .NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/SignatureSessionRestCoreController.cs).
Em PHP, a implementação do tratamento de retorno seria algo como:
```php
$sessionId = $_GET['signatureSessionId'];
$session = $service->getSignatureSession($sessionId);
if ($session->status != SignatureSessionStatus::COMPLETED) {
header("Location: http://localhost:8000/");
exit;
}
```
Veja [exemplo de tratamento de retorno em PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/signature-session-rest-core/complete.php).
Em Java, a implementação do tratamento de retorno seria algo como:
```java
@Controller
public class SessionCallbackController {
...
public String index(
Model model,
@RequestParam(value="signatureSessionId") String sessionId
) throws Exception {
SignatureSession session = restPkiService.getSignatureSession(UUID.fromString(sessionId));
if (session.getStatus() != SignatureSessionStatus.COMPLETED) {
return "redirect:/";
}
// Os documentos da sessão trazem o arquivo assinado só no campo obsoleto "location";
// o DocumentModel de cada documento o traz no campo "url".
List documents = new ArrayList<>();
for (SignatureSessionDocument document : session.getDocuments()) {
documents.add(restPkiService.getDocumentModel(document.getId()));
}
model.addAttribute("documents", documents);
return "signature-callback/index";
}
}
```
Veja [exemplo de tratamento de retorno em Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/SignatureSessionRestCoreController.java).
Note que verificamos o estado (`Status`) da sessão e enviamos o usuário para um local padrão caso o estado não seja `Completed`.
> **Observação**
>
> Quando um usuário é redirecionado de volta à sua aplicação, a sessão terá um de dois possíveis estados: `Completed` ou `UserCancelled`, esse último ocorrendo quando a
> sessão é cancelada pelo usuário. Seu código deve sempre checar esse estado, pois é perfeitamente normal um usuário ser direcionado ao endereço de tratamento de
> retorno porque cancelou a sessão.
Em .NET, a view de Razor seria algo como:
```html
@using Lacuna.RestPki.Api
@using Lacuna.RestPki.Client
@model SignatureSession
Signed documents
```
Em PHP, o lado do cliente seria algo como:
```php
Signed documents
```
Em Java, a view de Spring seria algo como:
```html
Signed documents
```
O link de cada arquivo assinado (`Url`) é temporário: ele baixa o arquivo sem a chave de API e expira em pouco tempo, normalmente uma hora, então não o armazene.
Códigos mais antigos leem o mesmo link em `Location`, nome obsoleto desse campo. O cliente PHP só o expõe como `location`, e em Java os documentos da sessão
só o expõem como `getLocation()`; por isso o tratamento de retorno em Java acima obtém o `DocumentModel` de cada documento.
## Pré-definindo os documentos a serem assinados
As sessões que criamos até aqui solicitam que o usuário faça upload dos documentos a serem assinados. Você pode alternativamente especificar os documentos
que devem ser assinador no momento da criação da sessão. Nesse caso, o usuário não poderá fazer upload de documentos, podendo apenas assinar os documentos
pré-definidos pela sua aplicação.
Em .NET:
```cs
string pdfPath = ...;
Stream xmlStream = ...;
var documents = new List {
new SignatureSessionDocumentToSign {
File = FileReference.FromFile(pdfPath),
},
new SignatureSessionDocumentToSign {
File = FileReference.FromStream(xmlStream, "doc.xml"),
},
...
};
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
...
}, documents: documents);
return Redirect(response.RedirectUrl);
```
Em Java:
```java
CreateSignatureSessionRequest request = new CreateSignatureSessionRequest();
String pdfPath = ...;
InputStream xmlStream = ...;
List documents = new ArrayList<>();
SignatureSessionDocumentToSign document = new SignatureSessionDocumentToSign();
document.setFile(FileReference.fromFile(pdfPath, "doc.pdf", "application/pdf"));
documents.add(document);
document = new SignatureSessionDocumentToSign();
document.setFile(FileReference.fromStream(xmlStream, "doc.xml", "application/xml"));
documents.add(document);
CreateSignatureSessionResponse response = restPkiService.createSignatureSession(request, null, null, documents);
return "redirect:" + response.getRedirectUrl();
```
Integração direta por API:
```plaintext
POST {endpoint}/api/signature-sessions
{
...
"documents": [
{
"file": {
"content": "Base64EncodedBytes==",
"name": "doc1.pdf"
}
},
{
"file": {
"url": "https://yourapp.com/doc2.pdf"
}
},
...
]
}
```
Para mais informações veja [Referenciando arquivos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/file-referencing).
## Desabilitando downloads
Caso tenha a impressão de que alguns de seus usuários, ao invés de concluirem a sessão e retornarem à sua aplicação, simplesmente fazem o download dos arquivos
assinados e fecham a aba do navegador, de modo que a sua aplicação não chega a tomar conhecimento dos documentos, você pode desabilitar a função de download dos
arquivos assinados durante a sessão de assinatura especificando `DisableDownloads = true` ao criar a sessão.
Em .NET:
```cs
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
...
DisableDownloads = true,
});
return Redirect(response.RedirectUrl);
```
Em PHP:
```php
$request = new CreateSignatureSessionRequest();
$request->returnUrl = "http://localhost:8080/session-callback";
$request->disableDownloads = true;
$response = $service->createSignatureSession($request);
header("Location: {$response->redirectUrl}");
```
Em Java:
```java
CreateSignatureSessionRequest request = new CreateSignatureSessionRequest();
request.setReturnUrl("http://localhost:60695/signature-session-rest-core/complete");
request.setDisableDownloads(true);
CreateSignatureSessionResponse response = restPkiService.createSignatureSession(request);
return "redirect:" + response.getRedirectUrl();
```
Integração direta por API:
```plaintext
POST {endpoint}/api/signature-sessions
{
...
"disableDownloads": true
}
```
Nesse caso, os usuários só conseguirão baixar os arquivos assinados na sua aplicação e, portanto, ficarão menos inclinados a interromper o processo prematuramente
fechando a aba do navegador.
> **Observação**
>
> Outra forma de lidar com esse problema é optar pelo **Fluxo com webhook** (veja seção abaixo)
## Fluxo com webhook
No fluxo padrão descrito acima, a sua aplicação toma conhecimento dos documentos assinados quando o usuário é redirecionado de volta com a *query parameter*
`signatureSessionId` adicionada à URL.
Uma alternativa a esse fluxo é tomar conhecimento dos documentos assinados através de [notificação de evento por webhook](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/webhooks). Nesse caso, pode-se
abrir a página de assinatura em uma nova aba, a qual é fechada ao final do processo, dispensando a implementação do "tratamento de retorno" (é necessário, entretanto,
implementar o recebimento de notificação de evento).
Esse fluxo tem a vantagem de não depender do retorno do usuário à sua aplicacão para tratar os documentos que são assinados. Além disso, esse fluxo facilita
o uso de [processamento em background](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/background-processing), recurso que melhora significativamente a experiência do usuário.
Para abrir a página de assinatura em outra aba, é preciso utilizar um *anchor* (elemento ``) com `target="_blank"`:
```html
Sign documents
```
> **Observação**
>
> Outra opção é abrir a aba com uma chamada ao `window.open()` em Javascript
Além disso, é preciso omitir o parâmetro `returnUrl` ao criar a sessão.
Em .NET:
```cs
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
ReturnUrl = null,
EnableBackgroundProcessing = true,
});
return Redirect(response.RedirectUrl);
```
Veja [exemplo do início do fluxo com webhook em .NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/SignatureSessionRestCoreController.cs).
Em PHP:
```php
$request = new CreateSignatureSessionRequest();
$request->returnUrl = null;
$request->enableBackgroundProcessing = true;
$response = $service->createSignatureSession($request);
header("Location: {$response->redirectUrl}");
```
Veja [exemplo do início do fluxo com webhook em PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/signature-session-rest-core/using-webhook.php).
Em Java:
```java
CreateSignatureSessionRequest request = new CreateSignatureSessionRequest();
request.setEnableBackgroundProcessing(true);
CreateSignatureSessionResponse response = restPkiService.createSignatureSession(request);
return "redirect:" + response.getRedirectUrl();
```
Veja [exemplo do início do fluxo com webhook em Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/SignatureSessionRestCoreController.java).
Integração direta por API:
```plaintext
POST {endpoint}/api/signature-sessions
{
"returnUrl": null,
"enableBackgroundProcessing": true
}
```
Dessa forma, a aba será fechada pelo Rest PKI Core ao final do processo.
## Melhorando a experiência do usuário
Você pode optar por alguns dos recursos abaixo para melhorar a experiência do usuário durante suas sessões de assinatura:
* [Requisitos de certificados](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/certificate-requirements) podem ser usados para restringir os certificados que o usuário poderá escolher
* [Validação de arquivos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/file-validation)
* [Processamento em background](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/background-processing)
* [Metadados de documentos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/document-metadata)
**Demonstrações e exemplos**
- Demonstração: [Sign a file with Rest PKI Core](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file-rest-pki-core)
- Sign a PDF with Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureCoreController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-core.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-rest-core/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_signature_rest_core.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_signature_rest_core_controller.rb)
- Signature session with Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/SignatureSessionRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/SignatureSessionRestCoreController.java) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/signature-session-rest-core/index.php)
- Signature session with predefined documents using Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/SignatureSessionPreDefRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/SignatureSessionPreDefRestCoreController.java)
# [Processamento em background - Sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/background-processing)
Por padrão, o Rest PKI Core fará com que o usuário aguarde que os documentos sejam processados antes de enviá-lo de volta à sua aplicação. Se você quiser oferecer
uma experiência mais ágil para seus usuários, habilite o processamento em background especificando `EnableBackgroundProcessing = true` na criação da sessão.
Em .NET:
```cs
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
...
EnableBackgroundProcessing = true,
});
return Redirect(response.RedirectUrl);
```
Em PHP:
```php
$request = new CreateSignatureSessionRequest();
$request->enableBackgroundProcessing = true;
$response = $service->createSignatureSession($request);
header("Location: {$response->redirectUrl}");
```
Em Java:
```java
CreateSignatureSessionRequest request = new CreateSignatureSessionRequest();
request.setEnableBackgroundProcessing(true);
CreateSignatureSessionResponse response = restPkiService.createSignatureSession(request);
return "redirect:" + response.getRedirectUrl();
```
Integração direta por API:
```plaintext
POST {endpoint}/api/signature-sessions
{
...
"enableBackgroundProcessing": true
}
```
Nesse caso, o usuário será redirecionado de volta à sua aplicação assim que concluir as assinaturas, sem aguardar o processamento, Entretanto, o seu tratemento de
retorno precisa ser adequado para lidar com o caso de uma sessão de assinatura cujos documentos ainda não estão prontos para download.
> **Observação**
>
> Nenhuma adequação é necessária no caso do [Fluxo com webhook](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions#webhook-flow), pois nesse caso não há tratamento de retorno
## Estados adicionais
Além dos estados `Completed` e `UserCancelled`, a sessão pode estar em dois outros estados quando o usuário chega de volta à sua aplicação: `Processing` e
`ProcessingError`.
Enquanto uma sessão estiver em `Processing`, alguns documentos estarão com estado `Processing` e não estarão disponíveis para download. Apenas quando a sessão
mudar de estado para `Completed` é que todos os documentos estarão prontos para download.
> **Observação**
>
> O estado `ProcessingError` é uma condição rara que ocorre apenas caso o certificado do usuário seja revogado na janela de tempo entre o início da sessão
> e a conclusão do processamento. É perfeitamente apropriado que a sua aplicação ignore essa condição ou apenas lance uma exceção caso isso ocorra.
## Adaptando o tratamento de retorno
Vamos adaptar nosso tratamento de retorno para lidar com essas mudanças.
Em .NET:
```cs
public class SessionCallbackController : Controller {
...
public async Task Index(Guid signatureSessionId) {
var session = await restPkiService.GetSignatureSessionAsync(signatureSessionId);
if (session.Status != SignatureSessionStatus.Completed && session.Status != SignatureSessionStatus.Processing) {
return Redirect("/");
}
return View(session);
}
}
```
Em PHP:
```php
$sessionId = $_GET['signatureSessionId'];
$session = $service->getSignatureSession($sessionId);
if ($session->status != SignatureSessionStatus::COMPLETED && $session->status != SignatureSessionStatus::PROCESSING) {
header("Location: http://localhost:8000/");
exit;
}
```
Em Java:
```java
@Controller
public class SessionCallbackController {
...
public String index(
Model model,
@RequestParam(value="signatureSessionId") String sessionId
) throws Exception {
SignatureSession session = restPkiService.getSignatureSession(UUID.fromString(sessionId));
if (session.getStatus() != SignatureSessionStatus.COMPLETED &&
session.getStatus() != SignatureSessionStatus.PROCESSING) {
return "redirect:/";
}
// O link de cada arquivo assinado vem do DocumentModel do documento, depois que ele é concluído
Map signedFileUrls = new HashMap<>();
for (SignatureSessionDocument document : session.getDocuments()) {
if (document.getStatus() == DocumentStatus.COMPLETED) {
signedFileUrls.put(document.getId(), restPkiService.getDocumentModel(document.getId()).getSignedFile().getUrl());
}
}
model.addAttribute("session", session);
model.addAttribute("signedFileUrls", signedFileUrls);
return "signature-callback/index";
}
}
```
Note que agora também consideramos o estado `Processing` como normal. Com isso, podemos alterar a forma que os documentos serão mostrados.
Em .NET, alteraríamos a view de Razor da seguinte maneira:
```html
@using Lacuna.RestPki.Api
@using Lacuna.RestPki.Client
@model SignatureSession
Signed documents
@foreach (var document in Model.Documents)
{
@if (document.Status == DocumentStatus.Completed)
{
@document.SignedFile.Name
}
else
{
@document.OriginalFile.Name
(processing)
}
}
```
Em PHP, alteraríamos o lado do cliente da seguinte maneira:
```php
Signed documents
documents); $i++) {
$document = $session->documents[$i]; ?>
status == DocumentStatus::COMPLETED) { ?>
= $document->signedFile->name ?>
= $document->originalFile->name ?>
(processing)
```
Em Java, alteraríamos a view Thymeleaf do Spring da seguinte maneira:
```html
Signed documents
```
Note que renderizamos cada item da lista de maneira diferente baseado no estado do documento. O cliente PHP só expõe o link do arquivo assinado como
`location`, nome obsoleto do campo `url` (veja [Implementando o tratamento de retorno](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions#implementando-o-tratamento-de-retorno)).
# [Requisitos de certificados - Sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/certificate-requirements)
Para restringir os certificados que o usuário poderá escolher durante uma [sessão de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions), especifique uma lista de
**requisitos de certificado** ao criar a sessão. O usuário somente poderá escolher certificados que atendam a **todos** os critérios
especificados.
Cada requisito é composto por um **tipo** e, dependendo do tipo, um **argumento**. Por exemplo, digamos que a sua aplicação precisa que
as assinaturas sejam feitas com certificados A3, ficando vedado o uso de certificados A1, que é o que o requisito [CryptoDevice](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/certificate-requirements#crypto-device)
faz. Como esse requisito não recebe um argumento, precisamos apenas passar o tipo.
Em .NET:
```cs
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
...,
CertificateRequirements = new List {
new CertificateRequirement { Type = CertificateRequirementTypes.CryptoDevice }
}
});
return Redirect(response.RedirectUrl);
```
Integração direta por API:
```plaintext
POST {endpoint}/api/signature-sessions
{
...,
"certificateRequirements": [
{
"type": "CryptoDevice"
}
]
}
```
Veja os requisitos suportados na próxima sessão.
## Requisitos suportados
Os seguintes tipos de requisito são atualmente suportados:
* [CryptoDevice](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/certificate-requirements#crypto-device): requer que o certificado tenha a chave privada protegida por dispositivo criptográfico (certificado A3 ou A4)
* [Cpf](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/certificate-requirements#cpf): requer que o certificado tenha um determinado CPF
> **Observação**
>
> Se você precisar de um requisito diferente, contate-nos
### CryptoDevice
Use o requisito `CryptoDevice` para permitir apenas o uso de certificados cuja chave privada é protegida por dispositivo criptográfico (tipicamente
token ou smartcard). Em outras palavras, fica vedado o uso de certificados A1, podendo apenas serem utilizados certificados A3 ou A4. Este requisito
não recebe argumento.
### Cpf
Use o requisito `Cpf` para restringir o usuário a utilizar apenas certificados com um determinado CPF. Passe o CPF no argumento, com ou sem pontuação
(mas *com* zeros à esquerda).
> **Observação**
>
> Esse requisito tem o efeito colateral de melhorar a experiência do usuário no uso de certificado em nuvem, na medida em que permite que o sistema
> não precise solicitar ao usuário que digite seu CPF para consultar os certificados em nuvem disponíveis (o CPF é obtido a partir do requisito especificado).
# [Metadados de documentos - Sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/document-metadata)
Ao criar uma [sessão de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions), é possível especificar uma coleção de metadados a serem associados aos documentos
criados durante a sessão. Metadados são especificados por um *nome* e uma coleção de *valores*.
Em .NET:
```cs
var metadata = new NameValueCollection {
["organization"] = "Patorum Inc.",
["priority"] = "10",
};
// Multi-valued metadata also supported
metadata.Add("categories", "Document");
metadata.Add("categories", "Forms");
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
...
}, metadata: metadata);
```
Em .PHP:
```php
$metadata = [
"organization" => "Patorum Inc.",
"priority" => "10"
];
// Multi-valued metadata also supported
$metadata["categories"] = ["Document", "Forms"];
$request = new CreateSignatureSessionRequest();
...
$request->documentMetadata = $metadata;
```
Em Java:
```java
CreateSignatureSessionRequest request = new CreateSignatureSessionRequest();
...
List firstElement = new ArrayList<>();
firstElement.add("Patorum Inc.");
request.putDocumentMetadataItem("organization", firstElement);
List secondElement = new ArrayList<>();
secondElement.add("10");
request.putDocumentMetadataItem("priority", secondElement);
// Multi-valued metadata also supported
List listOfElements = new ArrayList<>();
listOfElements.add("Document");
listOfElements.add("Forms");
request.putDocumentMetadataItem("categories", listOfElements);
```
## Regras e limites
* Nomes de metadados podem conter apenas letras, dígitos, hífens (`-`), *underscores* (`_`) e espaços (recomenda-se o uso de identificadores como `some-info`, `SomeInfo` ou `some_info` ao invés de textos legíveis)
* Nomes de metadados podem ter no máximo 50 caracteres
* Cada valor de metadado pode ter no máximo 500 caracteres
> **Observação**
>
> Nas APIs de busca de documentos por metadado o batimento de nomes é ***case insensitive*** (não há diferenciação entre maiúsculas e minúsculas)
# [Validação de arquivos - Sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/file-validation)
Você pode validar os arquivos escolhidos pelo usuário durante as sessões de assinatura utilizando os mecanismos descritos nesse artigo.
## Regra de formação de nomes de arquivos
Caso você tenha uma regra de formação para os nomes de arquivos que são aceitos, você pode configurar sua conta do Rest PKI Core para validar cada arquivo
selecionado pelo usuário usando uma expressão regular que represente a sua regra de formação.
Considere a situação hipotética em que somente sejam aceitos arquivos com nome começando com a letra `P` ou `X`, seguido de 6 dígitos e extensão `.pdf`.
Para validar essa condição, configurar-se-ia a regra de formação para:
```plaintext
^[PX]\d{6}\.pdf$
```
O usuário ficaria então impedido de escolher arquivos com nome fora desse padrão:

> **Observação**
>
> Nosso [suporte ao desenvolvedor](mailto:suporte@lacunasoftware.com) pode auxiliá-lo a montar a expressão regular a partir de uma descrição textual da sua
> regra de formação
## API de validação de arquivos
Também é possível configurar uma API de validação de arquivos, implementada pela sua aplicação, a ser chamada para validar cada arquivo escolhido pelo usuário.
A sua aplicação recebe o nome do arquivo (além do *MIME type* e tamanho em bytes) e retorna se o arquivo deve ou não ser aceito (e, caso negativo, o motivo da
rejeição).
A solicitação de validação consiste em uma requisição HTTP com verbo `POST` tendo como *payload* um objeto `ValidateFileRequest` (consulte a
[Referência da API](https://core.pki.rest/swagger) para a definição do modelo). Veja um exemplo abaixo:
```json
{
"name": "P123456.pdf",
"contentType": "application/pdf",
"length": 2476032
}
```
A sua aplicação deve responder com um `ValidateFileResponse` conforme abaixo:
```json
{
"accept": true,
"rejectReason": null
}
```
Ou, em caso de rejeição:
```json
{
"accept": false,
"rejectReason": "Mensagem para o usuário"
}
```
A mensagem retornada pela sua aplicação será exibida para o usuário, por exemplo:

**Demonstrações e exemplos**
- Validate files uploaded to a Rest PKI Core signature session: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/ValidateFileRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ValidateFileRestCoreController.java) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/validate-file-rest-core/index.php)
# [Conversão para PDF/A - Sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/pdf-a-conversion)
A partir da versão 4.2.0, o Rest PKI Core pode converter automaticamente um documento PDF para o formato **PDF/A** (padrão de
arquivamento de longo prazo) logo antes da assinatura. Para isso, especifique `ConvertToPdfA = true` em `PdfSignatureOptions`.
> **Aviso**
>
> Esta operação requer no mínimo a versão **4.2.2** da biblioteca cliente `Lacuna.RestPkiCore.Client`. Certifique-se de
> atualizar o pacote antes de utilizá-la.
> **Observação**
>
> Essa funcionalidade depende de um serviço de conversão para PDF/A previamente habilitado e configurado na sua instância do
> Rest PKI Core. Caso o serviço não esteja disponível, a chamada falhará com uma `ApiException` (código `PdfAConversionNotAvailable`)
> ao tentar realizar a conversão.
## Em sessões de assinatura
Ao criar uma [sessão de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions), informe `PdfSignatureOptions.ConvertToPdfA = true` para converter para PDF/A todos
os documentos PDF assinados durante a sessão (tanto os enviados pelo usuário quanto os [pré-definidos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions#predefined-documents)
que não especifiquem sua própria opção):
Em .NET:
```cs
var response = await restPkiService.CreateSignatureSessionAsync(new CreateSignatureSessionRequest() {
...
PdfSignatureOptions = new PdfSignatureOptions {
ConvertToPdfA = true,
},
});
return Redirect(response.RedirectUrl);
```
Também é possível especificar a opção individualmente em cada documento pré-definido, sobrepondo a opção definida no nível da
sessão:
```cs
var documents = new List {
new SignatureSessionDocumentToSign {
File = FileReference.FromFile(pdfPath),
PdfSignatureOptions = new PdfSignatureOptions {
ConvertToPdfA = true,
},
},
...
};
```
Integração direta por API:
```plaintext
POST {endpoint}/api/signature-sessions
{
...
"pdfSignatureOptions": {
"convertToPdfA": true
}
}
```
## Na assinatura avulsa
A mesma opção está disponível em `PdfSignatureOptions` ao usar o fluxo de assinatura local (`PrepareSignatureAsync`), fora do
contexto de sessões de assinatura.
# [Sessões de biometria - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions)
**Sessões de biometria** são uma funcionalidade do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) que permitem que seus usuários realizem operações biométricas, tais como prova de vida (Liveness), cadastro, autenticação e identificação facial, além de capturas de documentos de identidade.
## Fluxos de FrontEnd
O [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) já fornece uma tela de captura biométrica, que poderá ser utilizada na sua aplicação por meio de um redirecionamento ou através da utilização do nosso Widget.
### Fluxo de Redirecionamento (ReturnUrl)
Neste modelo, sua aplicação "perde o foco". O usuário é enviado para uma URL do RestPkiCore para capturar a face.
Para uitilizá-lo, você deve passar o parâmetro **`ReturnUrl`** na criação da sessão.
- **Quando usar:** Se você quer uma implementação mais simples, e não existe problema em redirecionar o usuário para completar o fluxo.
- **Vantagens:** Implementação mais simples, não há necessidade de configurações avançadas como CSP (Content Security Policies).
- **O que acontece após o fim:** O usuário é redirecionado para a URL que você informou no `ReturnUrl`, trazendo na _Query String_ o `completeTicket` (necessário para você consultar o resultado depois).
#### Diagrama

##### 1. Preparação (Backend)
Tudo começa quando o seu usuário clica para iniciar a biometria no seu site.
- `startLiveness()`: O seu Frontend avisa o seu Backend que uma sessão precisa ser iniciada.
- `StartLivenessSessionAsync`: O Backend da sua aplicação faz uma requisição para a API do RestPkiCore.
- `sessionUrl`: O RestPkiCore retorna uma URL única da sessão. Seu backend repassa essa URL para o seu frontend.
##### 2. Ação do Usuário (Frontend + RedirectUrl)
Agora é o momento em que a câmera é aberta e o usuário interage.
- `redirect to sessionUrl`: O seu Frontend redireciona para a página de sessão de biometria, a página assume o controle abre a câmera, orienta o usuário e captura a face/documento.
- `RedirectUrl + completeTicket`: Assim que o usuário termina, a janela fecha a câmera e carrega a janela com o `redirectUrl + ticket` através de query params.
##### 3. Validação e Resultado (Backend)
Agora o seu sistema precisa conferir se o usuário passou no teste.
- `CompleteLiveness(completeTicket)`: O seu Frontend envia o Ticket para o seu próprio Backend.
- `CompleteLivenessSession(completeTicket)`: O seu Backend envia esse ticket para o RestPkiCore.
- `LivenessSessionStatus`: O RestPkiCore responde ao seu Backend com o veredito (Sucesso, Falha e os dados capturados).
- `Success/Fail`: Finalmente, o seu Backend responde ao seu Frontend confirmando se a operação foi aprovada, permitindo que o usuário siga no seu fluxo (ex: liberar um pagamento ou login).
### Fluxo Incorporado (Widget/TrustedOrigin)
Neste modelo, o usuário permanece durante todo o tempo dentro da página da sua aplicação, pois a tela de captura é renderizada dentro do seu próprio site através da utilização do nosso Widget.
Para utilizá-lo, você deve passar o parâmetro **`TrustedOrigin`** na criação da sessão.
> **Observação**
>
> O `TrustedOrigin` deve ser a URL base do seu site (ex: `https://meusite.com`). Isso funciona como uma proteção do ambiente JavaScript, garantindo que o widget só funcione no seu domínio.
- **Quando usar:** Se você quer manter o usuário dentro do seu ambiente para uma experiência mais fluida.
- **Vantagens:** O fluxo é integrado, não é necessário redirecionar o usuário nem processar parâmetros de retorno (_Query String_).
- **O que acontece após o fim:** O usuário permanece na tela da sua aplicação, o resultado da sessão (`completeTicket`) vem na própria chamada do componente.
#### Diagrama

##### 1. Preparação (Backend)
Tudo começa quando o seu usuário clica para iniciar a biometria no seu site.
- `startLiveness()`: O seu Frontend avisa o seu Backend que uma sessão precisa ser iniciada.
- `StartLivenessSessionAsync`: O Backend da sua aplicação faz uma requisição para a API do RestPkiCore.
- `sessionUrl`: O RestPkiCore retorna uma URL única da sessão. Seu backend repassa essa URL para o seu frontend.
##### 2. Ação do Usuário (Frontend + Widget)
Agora é o momento em que a câmera é aberta e o usuário interage.
- `performBioSession(sessionUrl)`: O seu Frontend "alimenta" o Widget do RestPkiCore com a URL recebida. O Widget assume o controle, abre a câmera, orienta o usuário e captura a face/documento.
- `completeTicket`: Assim que o usuário termina, o Widget fecha a câmera e devolve para o seu Frontend um "Ticket".
##### 3. Validação e Resultado (Backend)
Agora o seu sistema precisa conferir se o usuário passou no teste.
- `CompleteLiveness(completeTicket)`: O seu Frontend envia o Ticket para o seu próprio Backend.
- `CompleteLivenessSession(completeTicket)`: O seu Backend envia esse ticket para o RestPkiCore.
- `LivenessSessionStatus`: O RestPkiCore responde ao seu Backend com o veredito (Sucesso, Falha e os dados capturados).
- `Success/Fail`: Finalmente, o seu Backend responde ao seu Frontend confirmando se a operação foi aprovada, permitindo que o usuário siga no seu fluxo (ex: liberar um pagamento ou login).
#### Instalação e Configuração do Widget
Para integrar a captura biométrica ao seu frontend, você pode utilizar o Rest PKI Widget. Existem duas formas de adicioná-lo ao seu projeto: via **CDN** (direto no HTML) ou via **NPM** (para projetos que utilizam frameworks como React, Angular ou Vue).
##### 1. Via CDN
Esta é a forma mais rápida de começar. Basta adicionar a tag `script` no `head` do seu arquivo HTML.
```html
```
##### 2. Via NPM
Se você utiliza frameworks como React, Angular ou Vue, instale o pacote via terminal para garantir o controle de versões e tipagens, pode ser [encontrada no respositorio](https://www.npmjs.com/package/lacuna-restpki-widget):
```shell
npm i lacuna-restpki-widget
```
Você pode verificar no arquivo `package.json` se a dependencia foi instalada
```json
{
...
"dependencies": {
"lacuna-restpki-widget": "x.y.z",
},
...
}
```
Após a instalação, você pode importar o widget no seu componente ou arquivo JavaScript:
```js
import { RestPkiWidget } from 'lacuna-restpki-widget';
```
---
## Configuração do Backend
Para começar, você precisará dos seguintes parâmetros:
* **Endpoint**: endereço da instância do Rest PKI Core a ser utilizada
* **Chave de API**: chave de autenticação com a API
Se estiver utilizando o Rest PKI Core como um serviço (SaaS), solicite seus parâmetros ao nosso [suporte ao desenvolvedor](mailto:suporte@lacunasoftware.com).
Caso esteja utilizando uma [instância própria](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises), o *endpoint* é o próprio endereço do painel de controle do Rest PKI, por exemplo
`https://assinatura.suaempresa.com.br/`. Crie você mesmo uma chave de API seguindo os passos abaixo:
1. Autentique-se no painel de controle da sua instância
2. No menu lateral, clique em **Aplicações**, em seguida em **Adicionar**
3. Preencha um **nome** para a aplicação
4. Marque o papel `Operador` (este papel é suficiente para realizar as operações de integração mais comuns, como criar sessões de assinatura)
5. Clique em **Criar**
6. Na página de detalhes da aplicação, clique em **Chaves**, em seguida em **Adicionar**
7. Preencha uma descrição qualquer para a chave e escolha uma expiração (recomenda-se escolher **Nunca expira**) e clique em **Criar**
8. **Tome nota da chave de API exibida** pois não será possível recuperá-la mais tarde
### Chamando a API
Embora o Rest PKI Core ofereça APIs REST que podem ser facilmente chamadas, elas normalmente não são utilizadas diretamente. Ao invés disso, oferecemos bibliotecas para consumir os serviços do Rest PKI Core (*client libs*) em diversas linguagens de programação e diversos projetos de exemplos que demonstram o uso dessas bibliotecas, de modo que os programadores não precisem se preocupar com os detalhes envolvidos no consumo de APIs e possam codificar diretamente em sua linguagem preferida.
Utilização das bibliotecas (ClientLibs):
* [C#/.NET](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/client-libs/dotnet)
Se você precisar de um exemplo em uma linguagem que não foi listada, por favor entre em contato conosco em [suporte@lacunasoftware.com](mailto:suporte@lacunasoftware.com) ou EQN 102/103, Ed. Avenida 102, 2º Andar - Asa Norte, Brasília-DF, Brasil. Telefone: +55 61 3030-5700.
## Parametros das sessões de biometria
Ao iniciar uma sessão, você deve configurar os parâmetros que definem como o usuário irá interagir com o sistema e quais regras de segurança serão aplicadas.
### Parâmetros gerais
- **TrustedOrigin:** Parâmetro obrigatório para o fluxo Incorporado. Deve ser a URL do domínio onde o widget será carregado (ex: https://meu-site.com.br). Isso impede que sua sessão seja roubada e executada em sites maliciosos.
- **ReturnUrl:** Parâmetro obrigatório para o Fluxo de Redirecionamento. É a URL para onde o usuário será enviado após concluir a biometria. O resultado (ticket) será anexado a esta URL via Query String.
- **PlatformPreference:** Define a preferência de plataforma e configuração do QRCode.
- **NoPreference**: O sistema permite realizar a sessão tanto pelo computador quanto por um dispositivo móvel, sem exibir nenhum QR Code.
- **PreferMobile**: O sistema permite iniciar a sessão pelo computador, mas recomenda que seja realizado por meio de um dispositivo móvel exibindo um QR code para realizar a sessão.
- **RequireMobile**: Bloqueia o uso em desktops, exigindo um dispositivo móvel exibindo um QR code para realizar a sessão.
### Identificador dos usuários (`SubjectIdentifier`)
O `SubjectIdentifier` é um campo que vincula a sessão de biometria a uma pessoa específica que está utilizando o seu sistema.
Para as sessões de cadastro biométrico e autenticação biométrica, o `SubjectIdentifier` é o identificador único relacionado à aquela pessoa que você deseja cadastrar ou autenticar no sistema de biometria.
Para as sessões anônimas, como Liveness e captura de documentos, esse identificador é indexado e poderá ser utilizado para encontrar o histórico de sessões com aquele identificador.
> **Dica**
>
> Recomendamos a utilização de identificadores únicos e constantes, como CPF (apenas números), CNPJ, E-mail ou o UUID do seu sistema.
Caso queira aceitar apenas alguns tipos de identificadores específicos, você pode [configurar os formatos de identificador permitidos para sua Subscription](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/configs/subject-identifier-formats).
### Parâmetros das sessões com captura facial
- **FaceCaptureProvider:** Define qual tecnologia de captura será utilizada na sessão de biometria: `FaceTecLiveness3d` ou `FortFaceSdkLiveness`, desde que o provedor esteja habilitado na instância.
- Se não for informado, vale o provedor preferido da organização, se houver, ou o padrão da instância, `FaceTecLiveness3d`.
- Exemplos mais antigos usam o parâmetro `FaceLivenessProvider`, que está obsoleto: use `FaceCaptureProvider`.
### Parâmetros de geolocalização
O Rest PKI Core pode capturar a localização geográfica do dispositivo do usuário durante a sessão de biometria. O recurso está **desabilitado por padrão**, essa é uma característica do software e pode ser habilitado por sessão, globalmente na configuração da subscription ou, em instâncias próprias (on-premises), diretamente no `appsettings` (seção `Bio:Geolocation`).
> **Dica**
>
> Prefere configurar o padrão pelo painel? Veja [Configuração de geolocalização](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/configs/geolocation).
- **`GeolocationCaptureType`**: Define o comportamento da captura.
- **`Disabled`** (padrão): Geolocalização não é coletada.
- **`Optional`**: O sistema tenta capturar a geolocalização, mas a sessão prossegue normalmente caso o usuário negue permissão ou a captura falhe.
- **`Required`**: A geolocalização é obrigatória. A sessão é interrompida se a captura não for concluída com sucesso.
- **`BestEffort`**: O sistema tenta capturar a geolocalização sem nunca interromper a sessão. Falhas transitórias (ex.: timeout) são tentadas novamente automaticamente, por padrão até 3 tentativas no total; falhas permanentes (ex.: permissão negada) não são reenviadas. Se todas as tentativas falharem, a sessão prossegue normalmente sem a localização.
- **`GeolocationCapturePolicy`**: Define em quais dispositivos a geolocalização é coletada (relevante para sessões com QR code).
- **`CollectOnCaptureDevice`** (padrão): A geolocalização é capturada apenas no dispositivo que realiza a biometria (ex: o celular, nos fluxos com QR code).
- **`CollectOnAllDevices`**: A geolocalização é capturada em todos os dispositivos envolvidos na sessão — tanto no desktop que iniciou quanto no celular que realizou a captura.
> **Observação**
>
> Quando `GeolocationCaptureType` é `BestEffort`, por padrão o usuário vê uma etapa pedindo a permissão de localização, porém sem os botões de cancelar ou pular — já que essa captura nunca pode interromper a sessão. Esse comportamento pode ser desativado pelo painel, removendo essa etapa própria do Rest PKI Core — veja [Configuração de geolocalização](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/configs/geolocation). Mesmo assim, o navegador pode exibir seu próprio pedido de permissão caso o usuário ainda não tenha decidido sobre ela (ver nota abaixo).
> **Observação**
>
> A exibição (ou não) do pedido de permissão, e por quanto tempo o navegador lembra da permissão concedida, são definidos por cada navegador e fogem ao controle do Rest PKI Core. Em alguns navegadores, conceder a permissão de forma temporária (ex.: "Permitir desta vez" no Chrome) pode manter a localização disponível durante a sessão do navegador, mesmo quando a captura é opcional. Para detalhes sobre cada comportamento, consulte a documentação do respectivo navegador.
## Tipos de sessão
* [Prova de vida (`Liveness`)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/liveness)
* [Cadastro biométrico (`Enrollment`)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/enrollment)
* [Autenticação facial (`Authentication`)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/authentication)
* [Identificação facial (`Identification`)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/identification)
* [Captura de documento (`IdentificationDocumentCapture`)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/id-capture)
## Veja também
* [Repositório de exemplos(GitHub)](https://github.com/LacunaSoftware/RestBioSamples)
* [Exemplo de backend (.NET)](https://github.com/LacunaSoftware/RestBioSamples/tree/main/backend/dotnet)
* [Exemplo de backend (Java)](https://github.com/LacunaSoftware/RestBioSamples/tree/main/backend/java)
* [Exemplo de frontend (Angular)](https://github.com/LacunaSoftware/RestBioSamples/tree/main/frontend/angular)
* [Exemplo de frontend (JS/HTML)](https://github.com/LacunaSoftware/RestBioSamples/tree/main/frontend/generic)
# [Sessões de biometria - Rest PKI Core - Client Lib .NET/C](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/client-libs/dotnet)
Antes de explorar os casos de uso, é necessário configurar a biblioteca do Rest PKI Core em sua aplicação. A biblioteca `Lacuna.RestPkiCore.Client` é compatível
com o .NET Standard 2.0. Os exemplos abaixo usam ASP.NET Core, como o [exemplo de backend (.NET)](https://github.com/LacunaSoftware/RestBioSamples/tree/main/backend/dotnet),
que usa o .NET 8.
## 1. Configuração do ambiente
No seu arquivo `appsettings.json`, adicione as credenciais obtidas no painel do Rest PKI Core:
```json
{
...,
"RestPki": {
"Endpoint": "SEU_ENDPOINT_AQUI",
"ApiKey": "SUA_CHAVE_DE_API_AQUI"
}
}
```
No arquivo Program.cs, registre o serviço antes da chamada de builder.Build():
``` CS
// Registra o cliente do Rest PKI Core utilizando a seção do appsettings
builder.Services.AddRestPki().Configure(builder.Configuration.GetSection("RestPki"));
var app = builder.Build();
```
## 2. Injeção de Dependência
Para utilizar as funcionalidades de biometria em seus Controllers, injete a interface IRestBioService via construtor.
> **Observação**
>
> A interface IRestBioService é a responsável especificamente por todas as operações de sessões biométricas (Liveness, Cadastro, Autenticação, etc).
```CS
using Lacuna.RestPki.Client;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/sessions")]
public class MyController : ControllerBase {
private readonly IRestBioService _restBioService;
public MyController(IRestBioService restBioService) {
_restBioService = restBioService;
}
// Os métodos de exemplo serão implementados aqui...
}
```
## Veja também
* [Exemplo de backend (.NET)](https://github.com/LacunaSoftware/RestBioSamples/tree/main/backend/dotnet)
* [Repositório de exemplos(GitHub)](https://github.com/LacunaSoftware/RestBioSamples)
# [Configuração de geolocalização (Geolocation) - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/configs/geolocation)
Durante uma sessão de biometria, o Rest PKI Core pode capturar a localização geográfica do dispositivo do usuário (latitude e longitude). O recurso vem **desligado por padrão**, essa é uma característica do software e pode ser ligado pelo painel, passando a valer para todas as sessões da sua aplicação. Em instâncias próprias (on-premises), o mesmo padrão também pode ser definido diretamente no `appsettings`, na seção `Bio:Geolocation`.
Existem quatro configurações:
- **Tipo de captura de geolocalização** — define se a localização é capturada e se ela é obrigatória:
- **Desabilitado** (padrão): a localização não é capturada.
- **Opcional**: o sistema tenta capturar a localização, mas a sessão continua normalmente caso o usuário não dê permissão ou a captura falhe.
- **Obrigatório**: a sessão só prossegue se a localização for capturada com sucesso.
- **Melhor esforço**: o sistema tenta capturar a localização sem nunca interromper a sessão, reenviando automaticamente falhas transitórias (por padrão, até 3 tentativas). Se todas as tentativas falharem, a sessão continua normalmente sem a localização.
- **Política de captura de geolocalização** — define em quais aparelhos a localização é coletada (importante nos fluxos com QR code, em que a biometria é feita no celular). Só aparece quando a captura está ligada:
- **Coletar no dispositivo de captura** (padrão): coleta apenas no aparelho que faz a biometria (ex.: o celular).
- **Coletar em todos os dispositivos**: coleta tanto no computador que iniciou a sessão quanto no celular que fez a biometria.
- **Máximo de tentativas** — só aparece quando o **Tipo de captura de geolocalização** está definido como **Melhor esforço**. Número de tentativas de captura antes de a sessão continuar sem a localização (padrão: 3).
- **Exibir instruções na captura de geolocalização de melhor esforço** — só aparece quando o **Tipo de captura de geolocalização** está definido como **Melhor esforço**:
- **Habilitado** (padrão): o usuário vê uma etapa pedindo a permissão de localização, porém sem os botões de cancelar ou pular.
- **Desabilitado**: a captura acontece sem nenhuma etapa própria do Rest PKI Core. Mesmo assim, o navegador do usuário pode exibir seu próprio pedido de permissão de localização, caso ele ainda não tenha decidido sobre ela.
## Como configurar pelo painel
1. Autentique-se no painel de controle da sua instância.
2. No menu lateral, clique em **Configurações**.
3. Localize a seção **"Configurações de geolocalização da sessão de biometria"** e clique em **Editar**.

4. No campo **Tipo de captura de geolocalização**, escolha **Opcional**, **Obrigatório** ou **Melhor esforço** para ligar o recurso.
5. Se desejar, ajuste a **Política de captura de geolocalização** (esse campo só aparece quando a captura está ligada).

6. Se escolheu **Melhor esforço**, você também pode ajustar o **Máximo de tentativas** e definir a opção **Exibir instruções na captura de geolocalização de melhor esforço** como **Desabilitado**, caso não queira mostrar a etapa de permissão ao usuário.

7. Clique em **Salvar** para aplicar as configurações.
> **Dica**
>
> Integrando via API? Você pode definir a geolocalização por sessão, sobrescrevendo o padrão configurado aqui — veja [Parâmetros de geolocalização](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#geolocation).
# [Configuração para os formatos de identificador (SubjectIdentifier) - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/configs/subject-identifier-formats)
O `SubjectIdentifier` é um campo que vincula a sessão de biometria a uma pessoa específica que está utilizando o seu sistema.
Por padrão, o sistema aceita qualquer formato de identificador. Se quiser restringir quais formatos são permitidos nas sessões da sua aplicação, siga os passos abaixo:
1. Autentique-se no painel de controle da sua instância.
2. No menu lateral, clique em **Configurações**.
3. Localize a seção **"Configurações de biometria"**.

4. Clique em **Editar** e, em **Formatos de identificador permitidos**, selecione os formatos que deseja permitir: CPF, CNPJ e/ou E-mail.

5. Clique em **Salvar** para aplicar as configurações.
# [Autenticação facial (1:1 Authentication) - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/authentication)
- Compara o rosto capturado em tempo real com uma biometria previamente cadastrada, confirmando se o usuário é realmente quem afirma ser.
- A foto é coletada por meio de uma prova de vida (Liveness).
## Criação da sessão
Para criar uma sessão de autenticação, pode-se utilizar o método `StartAuthenticationSessionAsync` das ClientLibs, passando como parâmetro `StartBioAuthenticationSessionRequest`.
Esse método realiza uma requisição `POST` para a rota `/api/bio/sessions/authentication` [(Swagger)](https://core.pki.rest/swagger/index.html#operations-BioSessions-post_api_bio_sessions_authentication).
### Parâmetros (`StartBioAuthenticationSessionRequest`)
| **Parâmetro** | **Obrigatório?** | **Tipo** | **Descrição** |
| -------------------------------------- | ---------------- | -------- | --------------- |
| **ReturnUrl** | Condicional* | String | URL para redirecionar o usuário após a biometria. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxo-de-redirecionamento-returnurl)
| **TrustedOrigin** | Condicional* | String | URL do seu site onde o Widget está incorporado. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxo-incorporado-widgettrustedorigin)
| **PlatformPreference** | Não | Enum | Define a preferência de plataforma e configuração do QRCode. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#parâmetros-gerais)
| **FaceCaptureProvider** | Não | Enum | Define o provedor de biometria. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#parâmetros-das-sessões-com-captura-facial)
| **Subject** | Sim | Object | Objeto de referência do usuário
> **Dica**
>
> *Você deve informar pelo menos um dos dois parâmetros: [Veja a diferença entre ReturnUrl e TrustedOrigin](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxos-de-frontend)
> - `ReturnUrl` (para redirecionamento)
> - `TrustedOrigin` (para widget)
### Parâmetros (`Subject`)
| **Parâmetro** | **Obrigatório?** | **Tipo** | **Descrição** |
| -------------------------------------- | ---------------- | -------- | --------------- |
| **Id** | Condicional* | Guid | Id do cadastro gerado pelo RestPkiCore
| **Identifier** | Condicional* | String | Identificador externo do usuário.
> **Dica**
>
> *Você deve informar pelo menos um dos dois parâmetros:
> - (recomendado) `Subject.Identifier`: Se for preencher, utilize o mesmo `SubjectIdentifier` utilizado na sessão de cadastro.
> - (avançado) `Subject.Id`: Se você tiver guardado o Id do `Subject` retornado na sessão de cadastro, poderá utilizá-lo aqui, mas, atenção: caso o cadastro anterior seja deletado, um novo cadastro feito para o mesmo `SubjectIdentifier` irá gerar um Id diferente.
### Exemplo de resposta da requisição:
Ao fazer a requisição para `StartAuthenticationSessionAsync` obtemos a seguinte resposta
```json
{
"sessionType": "Authentication",
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sessionUrl": "string"
}
```
### Integração do Frontend
Uma vez que o seu backend obteve a `sessionUrl` da API, você deve encaminhá-la para o seu frontend. A partir daí, o fluxo segue de acordo com a sua escolha de interface:
- **Fluxo Incorporado (Widget)**: Sua aplicação passa a URL para o componente Javascript inicializar a captura dentro da sua página.
- **Fluxo de Redirecionamento**: Sua aplicação direciona o usuário para o link recebido, onde a captura ocorrerá.
> **Dica**
>
> Em caso de dúvidas sobre a implementação de cada modelo, consulte nossa documentação de [Fluxos de FrontEnd](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxos-de-frontend).
---
## Completando a sessão
Para concluir uma sessão de autenticação, pode-se utilizar o método `CompleteAuthenticationSessionAsync` das ClientLibs, passando como parâmetro `CompleteBioSessionRequest`.
Esse método realiza uma requisição `POST` para a rota `/api/bio/sessions/authentication/completion` [(Swagger)](https://core.pki.rest/swagger/index.html#operations-BioSessions-post_api_bio_sessions_authentication_completion).
Este método é o ponto final. O ticket pode ser usado apenas uma vez.
> **Dica**
>
> Utilize o Complete apenas ao final do processo, enviando o Ticket recebido para validar o resultado e encerrar a sessão.
### Exemplo de resposta da requisição:
```Json
{
"subjectIdentifier": "string",
"subjectId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"failure": "CaptureFailed",
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"success": true,
"resultDataAvailable": true
}
```
* **subjectIdentifier**: Identificador externo do usuário. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#identificador-dos-usuários-subjectidentifier)
* **subjectId**: ID do cadastro.
* **failure**: Informa se houve alguma falha ao realizar a sessão, consulte a lista de falhas.
* **sessionId**: ID da sessão do RestPKICore.
* **success**: Informa se a sessão de autenticação foi bem-sucedida.
* **resultDataAvailable**: Se true, indica que você pode buscar as fotos coletadas na sessão.
##### Principais Failures
| **Enum** | **Descrição** |
| --------------------------------- | -------------------------------------------------- |
| CaptureFailed | A captura da imagem não pôde ser finalizada. Geralmente ocorre por interrupção do usuário. |
| LivenessFailed | O usuário não passou no teste de Prova de Vida. O número de tentativas excedeu o excedeu o limite. |
| NoMatch | Correspondência insuficiente entre a biometria coletada e a biometria previamente cadastrada. |
| BadImage | A imagem capturada não possui qualidade suficiente para processamento. Pode ser causada por baixa iluminação, desfoque (blur), reflexos excessivos ou rosto parcialmente coberto. |
## Consultando o status
Você pode consultar o estado atual de uma sessão a qualquer momento utilizando o seu `sessionId`. Isso é útil para monitorar se o usuário já iniciou ou expirou a sessão antes mesmo de ela ser concluída.
> **Dica**
>
> Utilize o `GetAuthenticationSessionStatusAsync` para acompanhar o progresso de uma sessão ativa através do seu SessionId
O resultado da requisição para esse endpoint é exatamente igual ao [exemplo de retorno da requisição CompleteAuthenticationSessionAsync](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/authentication#exemplo-de-resposta-da-requisição-1).
**Demonstrações e exemplos**
- Demonstração: [Enroll and authenticate your face](https://try.lacunasoftware.com/pt/lacuna-bio/enroll-and-authenticate-your-face)
# [Cadastro biométrico (Enrollment) - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/enrollment)
- Realiza o registro biométrico do usuário no sistema por meio de uma captura facial com prova de vida (liveness).
- A imagem da face é processada e armazenada de forma criptografada para ser utilizada futuramente em operações de autenticação ou identificação.
## Criação da sessão
Para criar uma sessão de Enrollment, pode-se utilizar o método `StartEnrollmentSessionAsync` das ClientLibs, passando como parâmetro `StartBioEnrollmentSessionRequest`.
Esse método realiza uma requisição `POST` para a rota `/api/bio/sessions/enrollment` [(Swagger)](https://core.pki.rest/swagger/index.html#operations-BioSessions-post_api_bio_sessions_enrollment).
### Parâmetros (`StartBioEnrollmentSessionRequest`)
| **Parâmetro** | **Obrigatório?** | **Tipo** | **Descrição** |
| -------------------------------------- | ---------------- | -------- | --------------- |
| **ReturnUrl** | Condicional* | String | URL para redirecionar o usuário após a biometria. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxo-de-redirecionamento-returnurl)
| **TrustedOrigin** | Condicional* | String | URL do seu site onde o Widget está incorporado. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxo-incorporado-widgettrustedorigin)
| **PlatformPreference** | Não | Enum | Define a preferência de plataforma e configuração do QRCode. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#parâmetros-gerais)
| **SubjectIdentifier** | Sim | String | Identificador externo do usuário. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#identificador-dos-usuários-subjectidentifier)
| **FaceCaptureProvider** | Não | Enum | Define o provedor de biometria. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#parâmetros-das-sessões-com-captura-facial)
| **CaptureIdentificationDocument** | Não | Bool | Se habilitado, o usuário deverá capturar um documento de identidade logo após a prova de vida.
| **DangerousOverrideIfAlreadyEnrolled** | Não | Bool | Define se o sistema permite o sobrescrita de um cadastro.
> **Dica**
>
> *Você deve informar pelo menos um dos dois parâmetros: [Veja a diferença entre ReturnUrl e TrustedOrigin](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxos-de-frontend)
> - `ReturnUrl` (para redirecionamento)
> - `TrustedOrigin` (para widget)
### Exemplo de resposta da requisição:
Ao fazer a requisição para `StartEnrollmentSessionAsync` obtemos a seguinte resposta
```json
{
"sessionType": "Enrollment",
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sessionUrl": "string"
}
```
### Integração do Frontend
Uma vez que o seu backend obteve a `sessionUrl` da API, você deve encaminhá-la para o seu frontend. A partir daí, o fluxo segue de acordo com a sua escolha de interface:
- **Fluxo Incorporado (Widget)**: Sua aplicação passa a URL para o componente Javascript inicializar a captura dentro da sua página.
- **Fluxo de Redirecionamento**: Sua aplicação direciona o usuário para o link recebido, onde a captura ocorrerá.
> **Dica**
>
> Em caso de dúvidas sobre a implementação de cada modelo, consulte nossa documentação de [Fluxos de FrontEnd](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxos-de-frontend).
---
## Completando a sessão
Para concluir uma sessão de cadastro, pode-se utilizar o método `CompleteEnrollmentSessionAsync` das ClientLibs, passando como parâmetro `CompleteBioSessionRequest`.
Esse método realiza uma requisição `POST` para a rota `/api/bio/sessions/enrollment/completion` [(Swagger)](https://core.pki.rest/swagger/index.html#operations-BioSessions-post_api_bio_sessions_enrollment_completion).
Este método é o ponto final. O ticket pode ser usado apenas uma vez.
> **Dica**
>
> Utilize o Complete apenas ao final do processo, enviando o Ticket recebido para validar o resultado e encerrar a sessão.
### Exemplo de resposta da requisição:
```Json
{
"subjectIdentifier": "string",
"subjectId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"livenessStatus": {
"success": true,
"attemptCount": 2
},
"idCaptureStatus": {
"success": true,
"matchedFace": true,
"matchedFaceLevel": 1
},
"failure": "FaceNotFoundWithSufficientQuality",
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"success": true,
"resultDataAvailable": true
}
```
> **Dica**
>
> Repare que nesse exemplo o subjectId veio pois o cadastro não foi concluido, o código da failure nos que esse usuário já está cadastrado.
#### Detalhes dos campos:
* **subjectIdentifier**: Um nome ou ID para identificar o usuário da sessão.
* **subjectId**: ID do cadastro.
* **livenessStatus**:
* **success**: Informa se a prova de vida foi realizada com sucesso.
* **attemptCount**: Número de tentativas realizadas pelo usuário.
* **idCaptureStatus**:
* **success**: Informa se a captura do documento foi bem-sucedida.
* **matchedFace**: Indica se houve correspondência entre o rosto da pessoa e a foto no documento.
* **matchedFaceLevel**: Nível de similaridade entre o rosto e o documento (MatchLevel).
* **failure**: Informa se houve alguma falha ao realizar a sessão, consulte a lista de falhas.
* **success**: Informa se a sessão de cadastro foi bem-sucedida.
* **resultDataAvailable**: Se true, indica que você pode buscar as fotos coletadas na sessão.
##### Failures
| **Enum** | **Descrição** |
| --------------------------------- | -------------------------------------------------- |
| FaceNotFoundWithSufficientQuality | O sistema não conseguiu detectar um rosto com nitidez suficiente. Isso ocorre geralmente por má iluminação, desfoque (blur) ou uma câmera com qualidade inferior.
| FaceLiveness2dFailed | A análise de prova de vida 2D detectou que a face apresentada não é real.
| FaceCaptureFailed | Ocorreu um erro técnico ou interrupção durante o processo de captura da imagem, impedindo o processamento.
| DocumentCaptureFailed | Não foi possível capturar ou processar a imagem do documento de identidade.
| FaceIdentityVerificationFailed | A verificação externa de identidade falhou.
| DuplicatesFound | Durante um cadastro, o sistema identificou que esta biometria já pertence a outro usuário registrado na base.
| AttemptsLimitReached | O usuário excedeu o número máximo de tentativas permitidas para realizar a captura biométrica na sessão.
| SessionAborted | A sessão foi encerrada de maneira prematura pelo usuário ou pelo sistema antes da conclusão do processo.
## Consultando o status
Você pode consultar o estado atual de uma sessão a qualquer momento utilizando o seu `sessionId`. Isso é útil para monitorar se o usuário já iniciou ou expirou a sessão antes mesmo de ela ser concluída.
> **Dica**
>
> Utilize o `GetEnrollmentSessionStatusAsync` para acompanhar o progresso de uma sessão ativa através do seu SessionId
O resultado da requisição para esse endpoint é exatamente igual ao [exemplo de retorno da requisição CompleteEnrollmentSessionAsync](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/enrollment#exemplo-de-resposta-da-requisição-1).
**Demonstrações e exemplos**
- Demonstração: [Enroll and authenticate your face](https://try.lacunasoftware.com/pt/lacuna-bio/enroll-and-authenticate-your-face)
# [Captura de documento (IdentificationDocumentCapture) - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/id-capture)
- Realiza a captura da imagem de um documento de identificação, incluindo frente, verso e recorte da imagem do rosto.
> **Dica**
>
> Um documento também pode ser capturado na mesma sessão de [prova de vida](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/liveness) ou [cadastro biométrico](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/enrollment), utilizando o parâmetro `CaptureIdentificationDocument`. Nesses casos, também é feita uma comparação da foto da pessoa do documento com a biometria facial capturada.
## Criação da sessão
Para criar uma sessão de captura de documento, pode-se utilizar o método `StartIdentificationDocumentCaptureSessionAsync` das ClientLibs, passando como parâmetro `StartIdentificationDocumentCaptureSessionRequest`.
Esse método realiza uma requisição `POST` para a rota `/api/bio/sessions/id-capture` [(Swagger)](https://core.pki.rest/swagger/index.html#operations-BioSessions-post_api_bio_sessions_id_capture).
### Parâmetros (`StartIdentificationDocumentCaptureSessionRequest`)
| **Parâmetro** | **Obrigatório?** | **Tipo** | **Descrição** |
| --------------------------------- | ---------------- | -------- | ------------- |
| **ReturnUrl** | Condicional* | String | URL para redirecionar o usuário após a biometria. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxo-de-redirecionamento-returnurl)
| **TrustedOrigin** | Condicional* | String | URL do seu site onde o Widget está incorporado. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxo-incorporado-widgettrustedorigin)
| **PlatformPreference** | Não | Enum | Define a preferência de plataforma e configuração do QRCode. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#parâmetros-gerais)
| **SubjectIdentifier** | Não | String | Identificador externo do usuário. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#identificador-dos-usuários-subjectidentifier)
> **Dica**
>
> *Você deve informar pelo menos um dos dois parâmetros: [Veja a diferença entre ReturnUrl e TrustedOrigin](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxos-de-frontend)
> - `ReturnUrl` (para redirecionamento)
> - `TrustedOrigin` (para widget)
> Os parâmetros não obrigatórios podem ser deixados em branco (`null`) e serão automaticamente preenchidos pelos padrões do sistema.
### Exemplo de resposta da requisição:
```json
{
"sessionType": "IdentificationDocumentCapture",
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sessionUrl": "string"
}
```
### Integração do Frontend
Uma vez que o seu backend obteve a `sessionUrl` da API, você deve encaminhá-la para o seu frontend. A partir daí, o fluxo segue de acordo com a sua escolha de interface:
- **Fluxo Incorporado (Widget)**: Sua aplicação passa a URL para o componente Javascript inicializar a captura dentro da sua página.
- **Fluxo de Redirecionamento**: Sua aplicação direciona o usuário para o link recebido, onde a captura ocorrerá.
> **Dica**
>
> Em caso de dúvidas sobre a implementação de cada modelo, consulte nossa documentação de [Fluxos de FrontEnd](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxos-de-frontend).
---
## Completando a sessão
Para concluir uma sessão de captura de documento, pode-se utilizar o método `CompleteIdentificationDocumentCaptureSessionAsync` das ClientLibs, passando como parâmetro `CompleteBioSessionRequest`.
Esse método realiza uma requisição `POST` para a rota `/api/bio/sessions/id-capture/completion` [(Swagger)](https://core.pki.rest/swagger/index.html#operations-BioSessions-post_api_bio_sessions_id_capture_completion).
Este método é o ponto final. O ticket pode ser usado apenas uma vez.
> **Dica**
>
> Utilize o Complete apenas ao final do processo, enviando o Ticket recebido para validar o resultado e encerrar a sessão.
### Exemplo de resposta da requisição:
```json
{
"subjectIdentifier": "string",
"idCaptureStatus": {
"success": true,
"matchedFace": true,
"matchedFaceLevel": 0
},
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"success": true,
"resultDataAvailable": true
}
```
#### Detalhes dos campos:
* **subjectIdentifier**: Identificador externo do usuário. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#identificador-dos-usuários-subjectidentifier)
* **idCaptureStatus**:
* **success**: Informa se a captura do documento foi bem-sucedida.
* **matchedFace**: Indica se houve correspondência entre o rosto da pessoa e a foto no documento.
* **matchedFaceLevel**: Nível de similaridade entre o rosto e o documento (MatchLevel).
* **sessionId**: Identificador único da sessão.
* **success**: Resultado geral da sessão.
* **resultDataAvailable**: Se true, indica que você pode buscar as fotos coletadas na sessão.
---
## Consultando o status da sessão
Você pode consultar o estado atual de uma sessão a qualquer momento utilizando o seu `sessionId`. Isso é útil para monitorar se o usuário já iniciou ou expirou a sessão antes mesmo de ela ser concluída.
> **Dica**
>
> Utilize o `GetIdentificationDocumentCaptureSessionStatusAsync` para acompanhar o progresso de uma sessão ativa através do seu SessionId
O resultado da requisição para esse endpoint é exatamente igual ao [exemplo de retorno da requisição CompleteIdentificationDocumentCaptureSessionAsync](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/id-capture#exemplo-de-resposta-da-requisição-1).
# [Identificação facial (1:N Identification) - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/identification)
- Busca o rosto capturado em uma base de cadastros para descobrir a quem ele pertence.
- Ao contrário da autenticação, não parte de um usuário conhecido
> **Observação**
>
> A identificação facial é um processo computacionalmente custoso, também conhecido como **busca 1:N**.
> Dessa forma, pode acontecer que esse recurso não esteja habilitado na instância em que você esteja realizando a integração.
> Caso tenha interesse em habilitar esse recurso, **entre em contato conosco** via [suporte@lacunasoftware.com](mailto:suporte@lacunasoftware.com) para solicitar uma **demonstração**.
## Criação da sessão
Para criar uma sessão de identificação, pode-se utilizar o método `StartIdentificationSessionAsync` das ClientLibs, passando como parâmetro `StartBioIdentificationSessionRequest`.
Esse método realiza uma requisição `POST` para a rota `/api/bio/sessions/identification` [(Swagger)](https://core.pki.rest/swagger/index.html#operations-BioSessions-post_api_bio_sessions_identification).
### Parâmetros (`StartBioIdentificationSessionRequest`)
| **Parâmetro** | **Obrigatório?** | **Tipo** | **Descrição** |
| --------------------------------- | ---------------- | -------- | ------------- |
| **ReturnUrl** | Condicional* | String | URL para redirecionar o usuário após a biometria. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxo-de-redirecionamento-returnurl)
| **TrustedOrigin** | Condicional* | String | URL do seu site onde o Widget está incorporado. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxo-incorporado-widgettrustedorigin)
| **PlatformPreference** | Não | Enum | Define a preferência de plataforma e configuração do QRCode. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#parâmetros-gerais)
| **FaceCaptureProvider** | Não | Enum | Define o provedor de biometria. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#parâmetros-das-sessões-com-captura-facial)
> **Dica**
>
> *Você deve informar pelo menos um dos dois parâmetros: [Veja a diferença entre ReturnUrl e TrustedOrigin](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxos-de-frontend)
> - `ReturnUrl` (para redirecionamento)
> - `TrustedOrigin` (para widget)
> Os parâmetros não obrigatórios podem ser deixados em branco (`null`) e serão automaticamente preenchidos pelos padrões do sistema.
### Exemplo de resposta da requisição:
```json
{
"sessionType": "Identification",
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sessionUrl": "string"
}
```
### Integração do Frontend
Uma vez que o seu backend obteve a `sessionUrl` da API, você deve encaminhá-la para o seu frontend. A partir daí, o fluxo segue de acordo com a sua escolha de interface:
- **Fluxo Incorporado (Widget)**: Sua aplicação passa a URL para o componente Javascript inicializar a captura dentro da sua página.
- **Fluxo de Redirecionamento**: Sua aplicação direciona o usuário para o link recebido, onde a captura ocorrerá.
> **Dica**
>
> Em caso de dúvidas sobre a implementação de cada modelo, consulte nossa documentação de [Fluxos de FrontEnd](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxos-de-frontend).
---
## Completando a sessão
Para concluir uma sessão de identificação, pode-se utilizar o método `CompleteIdentificationSessionAsync` das ClientLibs, passando como parâmetro `CompleteBioSessionRequest`.
Esse método realiza uma requisição `POST` para a rota `/api/bio/sessions/identification/completion` [(Swagger)](https://core.pki.rest/swagger/index.html#operations-BioSessions-post_api_bio_sessions_identification_completion).
Este método é o ponto final. O ticket pode ser usado apenas uma vez.
> **Dica**
>
> Utilize o Complete apenas ao final do processo, enviando o Ticket recebido para validar o resultado e encerrar a sessão.
### Exemplo de resposta da requisição:
```JSON
{
"identifiedSubjectId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"identifiedSubjectIdentifier": "string",
"failure": "CaptureFailed",
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"success": true,
"resultDataAvailable": true
}
```
#### Detalhes dos campos:
* **identifiedSubjectId**: Em caso de sucesso, é preenchido com o Id do cadastro da pessoa identificada.
* **identifiedSubjectIdentifier**: Em caso de sucesso, é preenchido com o `SubjectIdentifier` da pessoa identificada.
* **failure**: Informa se houve alguma falha ao realizar a sessão, consulte a lista de falhas.
* **sessionId**: ID da sessão do RestPKICore.
* **success**: Informa se a sessão de identificação foi bem-sucedida.
* **resultDataAvailable**: Se true, indica que você pode buscar as fotos coletadas na sessão.
#### Failures
| **Enum** | **Descrição** |
| --------------------------------- | -------------------------------------------------- |
| CaptureFailed | A captura da imagem não pôde ser finalizada. Geralmente ocorre por interrupção do usuário.
| LivenessFailed | O usuário não passou no teste de Prova de Vida. O número de tentativas excedeu o excedeu o limite.
| NoMatch | Não houve correspondência mínima entre a biometria coletada e as biometrias cadastradas na base.
| BadImage | A imagem capturada não possui qualidade suficiente para processamento. Pode ser causada por baixa iluminação ou desfoque (blur).
## Consultando o status
Você pode consultar o estado atual de uma sessão a qualquer momento utilizando o seu `sessionId`. Isso é útil para monitorar se o usuário já iniciou ou expirou a sessão antes mesmo de ela ser concluída.
> **Dica**
>
> Utilize o `GetIdentificationSessionStatusAsync` para acompanhar o progresso de uma sessão ativa através do seu SessionId
O resultado da requisição para esse endpoint é exatamente igual ao [exemplo de retorno da requisição CompleteIdentificationSessionAsync](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/identification#exemplo-de-resposta-da-requisição-1).
# [Prova de vida (Liveness) - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/liveness)
- Verifica se há uma pessoa real presente do outro lado da tela, detectando tentativas de fraude com fotos, vídeos ou máscaras.
- Não realiza a identificação (quem é a pessoa), apenas atesta a presença de um ser humano vivo.
- A foto capturada pelo usuário pode ser recuperada no final da sessão.
## Criação da sessão
Para criar uma sessão de liveness, pode-se utilizar o método `StartLivenessSessionAsync` das ClientLibs, passando como parâmetro `StartLivenessSessionRequest`.
Esse método realiza uma requisição `POST` para a rota `/api/bio/sessions/liveness` [(Swagger)](https://core.pki.rest/swagger/index.html#operations-BioSessions-post_api_bio_sessions_liveness).
### Parâmetros (`StartLivenessSessionRequest`)
| **Parâmetro** | **Obrigatório?** | **Tipo** | **Descrição** |
| --------------------------------- | ---------------- | -------- | ------------- |
| **ReturnUrl** | Condicional* | String | URL para redirecionar o usuário após a biometria. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxo-de-redirecionamento-returnurl)
| **TrustedOrigin** | Condicional* | String | URL do seu site onde o Widget está incorporado. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxo-incorporado-widgettrustedorigin)
| **PlatformPreference** | Não | Enum | Define a preferência de plataforma e configuração do QRCode. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#parâmetros-gerais)
| **SubjectIdentifier** | Não | String | Identificador externo do usuário. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#identificador-dos-usuários-subjectidentifier)
| **FaceCaptureProvider** | Não | Enum | Define o provedor de biometria. [(veja mais detalhes)](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#parâmetros-das-sessões-com-captura-facial)
| **CaptureIdentificationDocument** | Não | Bool | Se habilitado, o usuário deverá capturar um documento de identidade logo após a prova de vida.
> **Dica**
>
> *Você deve informar pelo menos um dos dois parâmetros: [Veja a diferença entre ReturnUrl e TrustedOrigin](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxos-de-frontend)
> - `ReturnUrl` (para redirecionamento)
> - `TrustedOrigin` (para widget)
> Os parâmetros não obrigatórios podem ser deixados em branco (`null`) e serão automaticamente preenchidos pelos padrões do sistema.
> **Observação**
>
> Exemplos mais antigos usam o parâmetro `FaceLivenessProvider`, que está obsoleto: use `FaceCaptureProvider`. O Rest PKI Core só considera o
> `FaceLivenessProvider` quando o `FaceCaptureProvider` não é informado.
### Exemplo de resposta da requisição:
```json
{
"sessionType": "Liveness",
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sessionUrl": "string"
}
```
### Integração do Frontend
Uma vez que o seu backend obteve a `sessionUrl` da API, você deve encaminhá-la para o seu frontend. A partir daí, o fluxo segue de acordo com a sua escolha de interface:
- **Fluxo Incorporado (Widget)**: Sua aplicação passa a URL para o componente Javascript inicializar a captura dentro da sua página.
- **Fluxo de Redirecionamento**: Sua aplicação direciona o usuário para o link recebido, onde a captura ocorrerá.
> **Dica**
>
> Em caso de dúvidas sobre a implementação de cada modelo, consulte nossa documentação de [Fluxos de FrontEnd](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions#fluxos-de-frontend).
---
## Completando a sessão
Para concluir uma sessão de liveness, pode-se utilizar o método `CompleteLivenessSessionAsync` das ClientLibs, passando como parâmetro `CompleteBioSessionRequest`.
Esse método realiza uma requisição `POST` para a rota `/api/bio/sessions/liveness/completion` [(Swagger)](https://core.pki.rest/swagger/index.html#operations-BioSessions-post_api_bio_sessions_liveness_completion).
Este método é o ponto final. O ticket pode ser usado apenas uma vez.
> **Dica**
>
> Utilize o Complete apenas ao final do processo, enviando o Ticket recebido para validar o resultado e encerrar a sessão.
### Exemplo de resposta da requisição:
```JSON
{
"faceLivenessStatus": {
"provider": "FaceTecLiveness3d",
"success": true,
"attemptCount": 0
},
"idCaptureStatus": {
"success": true,
"matchedFace": true,
"matchedFaceLevel": 0
},
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"success": true,
"resultDataAvailable": true
}
```
#### Detalhes dos campos:
* **faceLivenessStatus**:
* **provider**: Provedor do serviço de biometria.
* **success**: Informa se a prova de vida foi realizada com sucesso.
* **attemptCount**: Número de tentativas realizadas pelo usuário.
* **idCaptureStatus***:
* **success**: Informa se a captura do documento foi bem-sucedida.
* **matchedFace**: Indica se houve correspondência entre o rosto da pessoa e a foto no documento.
* **matchedFaceLevel**: Nível de similaridade entre o rosto e o documento (MatchLevel).
* **sessionId**: Identificador único da sessão.
* **success**: Resultado geral da sessão.
* **resultDataAvailable**: Se true, indica que você pode buscar as fotos coletadas na sessão.
---
## Consultando o status da sessão
Você pode consultar o estado atual de uma sessão a qualquer momento utilizando o seu `sessionId`. Isso é útil para monitorar se o usuário já iniciou ou expirou a sessão antes mesmo de ela ser concluída.
> **Dica**
>
> Utilize o `GetLivenessSessionStatusAsync` para acompanhar o progresso de uma sessão ativa através do seu SessionId
O resultado da requisição para esse endpoint é exatamente igual ao [exemplo de retorno da requisição CompleteLivenessSessionAsync](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/liveness#exemplo-de-resposta-da-requisição-1).
**Demonstrações e exemplos**
- Demonstração: [Run a liveness check](https://try.lacunasoftware.com/pt/lacuna-bio/run-a-liveness-check)
# [Assinaturas em duas etapas - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/two-step-signatures)
Com as [sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions), o usuário assina numa página hospedada pelo [Rest PKI Core](https://docs.lacunasoftware.com/articles/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*).
1. A chave privada do certificado assina o hash: no navegador do usuário, com o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki), ou no seu servidor.
1. 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](https://docs.lacunasoftware.com/articles/web-pki/licensing).
* **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](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started). A chave de API da aplicação precisa de um papel que permita assinaturas, como o **Operador** (veja
> [Escolhendo os papéis](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#roles)).
## Preparando a assinatura
Em .NET, chame `IRestPkiService.PrepareSignatureAsync()` com o arquivo a assinar, o certificado do signatário e as opções da assinatura:
```cs
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](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/file-referencing)).
* 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()`:
```java
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:
```text
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.
1. O seu servidor [prepara a assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/two-step-signatures#preparando-a-assinatura) com esse certificado (`CertificateReference.FromBytes()` em .NET) e devolve à página o
hash a assinar, o algoritmo de hash e o estado.
1. 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](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/two-step-signatures#concluindo-a-assinatura).
```javascript
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](https://docs.lacunasoftware.com/articles/web-pki/get-started) do Web PKI.
### No seu servidor
Em .NET, assine o hash com a chave privada do certificado:
```cs
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:
```cs
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](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions) 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()`:
```java
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:
```text
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](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestCoreController.cs)
* .NET, com uma chave do servidor: [PadesServerKeyRestCoreController.cs](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesServerKeyRestCoreController.cs)
* Java, na sua própria página com o Web PKI: [PadesSignatureCoreController.java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureCoreController.java)
* Node.js, na sua própria página com o Web PKI: [pades-signature-core.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-core.js)
**Demonstrações e exemplos**
- Demonstração: [Sign a file with Rest PKI Core](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file-rest-pki-core)
- Sign a PDF with Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureCoreController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-core.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-rest-core/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_signature_rest_core.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_signature_rest_core_controller.rb)
- Sign multiple PDFs with Rest PKI Core: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/batch-pades-signature-core.js)
- Sign a PDF with a server key using Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesServerKeyRestCoreController.cs)
# [Códigos de erro - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/error-codes)
Alguns dos códigos de erro de API retornados pelo Rest PKI Core estão elencados abaixo:
Código | Descrição
---------------------------------------- | ---------
`DocumentNotFound` | Um documento referenciado não foi encontrado (verifique o ID do documento)
`SecurityContextNotFound` | Um contexto de segurança referenciado não foi encontrado (verifique o ID do contexto)
`SignatureSessionNotFound` | Uma sessão de assinatura referenciada não foi encontrada (verifique o ID da sessão)
`BadSignatureSessionOperation` | A operação não é válida para o estado atual da sessão de assinatura.Por exemplo, caso se tente fazer uma chamada para aguardar o fim do processamento de uma sessão de assinatura que ainda está no estado `Pending` esse erro é retornado
`BackgroundProcessing` | A operação não pode ser completada nesse momento porque o recurso ainda está sendo processado
`SignatureSessionTokenRequired` | O token de sessão de assinatura não foi passado no header `X-Signature-Session-Token` da requisição
`BadSignatureSessionToken` | Um token de sessão de assinatura inválido foi passado no header `X-Signature-Session-Token` da requisição
`ExpiredSignatureSessionToken` | Um token de sessão de assinatura expirado foi passado no header `X-Signature-Session-Token` da requisição
> **Observação**
>
> Os códigos acima são os mais comuns. Entretanto, essa lista não é exaustiva. Existem outros códigos e novos códigos podem ser adicionados sem aviso prévio.
# [Referenciando arquivos - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/file-referencing)
Existem diferentes maneiras de referenciar arquivos em chamadas à APIs do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core), por exemplo ao
[criar uma sessão de assinatura com documentos pré-definidos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions#predefined-documents).
## .NET
Você pode referenciar um arquivo por seu *path*:
```cs
var fileRef = FileReference.FromFile(@"C:\AppData\Documents\document.pdf");
```
Por um objeto `FileInfo` contendo o *path* do arquivo:
```cs
var fileInfo = new FileInfo(@"C:\AppData\Documents\document.pdf");
var fileRef = FileReference.FromFile(fileInfo);
```
Pelo conteúdo do arquivo, passando um array de bytes, nesse caso é preciso também informar o nome do arquivo:
```cs
byte[] bytes = ...;
var fileRef = FileReference.FromBytes(bytes, "document.pdf");
```
Por uma `stream`. Assim como em referências pelo conteúdo do arquivo, é preciso fornecer o nome do arquivo:
```cs
var fileRef = FileReference.FromStream(stream, "document.pdf");
```
Por URL absoluta. Nesse caso, o servidor da API irá baixar o arquivo do servidor da sua aplicação. A URL precisa ser
acessível publicamente com método `GET`. Quaisquer argumentos de autorização devem estar embutidos na própria URL:
```cs
var fileRef = FileReference.FromUrl("https://yourapp.com/docs/document1.pdf?token=xxxxxxxxx");
```
## Java
Você pode referenciar um arquivo por seu *path*:
```java
FileReference fileRef = FileReference.fromFile("C:/AppData/Documents/document.pdf", null, null);
```
Por um objeto `FileInfo` contendo o *path* do arquivo:
```java
File file = new File("C:/AppData/Documents/document.pdf");
File fileRef = FileReference.fromFile(file);
```
Pelo conteúdo do arquivo, passando um array de bytes, nesse caso é preciso também informar o nome do arquivo:
```java
byte[] bytes = ...;
FileReference fileRef = FileReference.fromBytes(bytes, "document.pdf", null);
```
Por uma `stream`. Assim como em referências pelo conteúdo do arquivo, é preciso fornecer o nome do arquivo:
```java
FileReference fileRef = FileReference.fromStream(stream, "document.pdf", "application/pdf");
```
Por URL absoluta. Nesse caso, o servidor da API irá baixar o arquivo do servidor da sua aplicação. A URL precisa ser
acessível publicamente com método `GET`. Quaisquer argumentos de autorização devem estar embutidos na própria URL:
```java
FileReference fileRef = FileReference.fromUrl("https://yourapp.com/docs/document1.pdf?token=xxxxxxxxx", null, null);
```
## Direct API integration
> **Observação**
>
> Os exemplos nessa seção exemplificam o referenciamento de arquivos numa criação de [sessão de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions),
> entretanto o mesmo se aplica a qualquer API que receba arquivos.
Você pode referenciar um arquivo incluindo seu conteúdo diretamente no request, codificado em Base64, nesse caso é preciso também fornecer o nome do arquivo:
```plaintext
POST {endpoint}/api/signature-sessions
{
...
"documents": [
{
"file": {
"content": "Base64EncodedBytes==",
"name": "doc1.pdf"
}
},
...
]
}
```
Também por URL absoluta. Nesse caso, o servidor da API irá baixar o arquivo do servidor da sua aplicação. A URL precisa ser
acessível publicamente com método `GET`. Quaisquer argumentos de autorização devem estar embutidos na própria URL:
```plaintext
POST {endpoint}/api/signature-sessions
{
...
"documents": [
{
"file": {
"url": "https://yourapp.com/docs/document1.pdf?token=xxxxxxxxx"
}
},
...
]
}
```
# [Webhooks - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/webhooks)
Você pode configurar sua conta no [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) para que sua aplicação receba notificações de eventos. Os seguintes eventos são
suportados atualmente:
Tipo do evento | Descrição
---------------------------- | -----------
`DocumentSignatureCompleted` | Disparado quando um documento é assinado e tem seu processamento concluído
Caso habilitadas, as notificações de eventos por webhook consistem em requisições HTTP `POST` com um `WebhookEventModel` em JSON. Consulte a
[Referência da API](https://core.pki.rest/swagger) para a definição do *schema* do modelo.
Abaixo temos um exemplo de notificação do tipo `DocumentSignatureCompleted`:
```json
{
"type": "DocumentSignatureCompleted",
"document": {
"id": "3dfdd6f0-884e-4092-abed-00f18ea6716a",
"key": "FUFYQ7SL6XKMBW9A",
"formattedKey": "FUFY-Q7SL-6XKM-BW9A",
"dateCreated": "2026-10-12T14:31:07.5310000-03:00",
"subscriptionId": "6e1ffc5e-4c0a-4f5b-9a0f-2d1b8c0f7a41",
"originalFile": {
"name": "P100001-001.pdf",
"mimeType": "application/pdf",
"length": 101730,
"url": "https://..."
},
"markedFile": null,
"signedFile": {
"name": "P100001-001.pdf",
"mimeType": "application/pdf",
"length": 273429,
"url": "https://..."
},
"signatureType": "Pdf",
"availableUntil": "2026-11-11T14:31:07.5310000-03:00",
"signers": [
{
"certificate": {
"thumbprint": "...",
"thumbprintSHA256": "...",
"subjectCommonName": "Pierre de Fermat:47363361886",
"subjectDisplayName": "Pierre de Fermat",
"subjectIdentifier": "47363361886",
"emailAddress": "test@lacunasoftware.com",
"organization": null,
"organizationIdentifier": null
},
"date": "2026-10-12T14:31:41.2070000-03:00"
}
],
"metadata": {},
"signatureSessionIds": [
"a9d1d2b7-0b0e-4d4b-8d4c-5b3a1f0c9e27"
]
}
}
```
O `document` é um `DocumentModel`, o mesmo modelo que `GET api/documents/{id}` retorna. O `signatureType` é `Pdf`, `Cms` ou `Xml`. A `url` de cada arquivo é
um link temporário que baixa o arquivo sem a chave de API e expira em pouco tempo, normalmente uma hora; por isso, baixe o arquivo em vez de armazenar o link.
Cada arquivo traz também `contentType` e `location`, cópias obsoletas de `mimeType` e `url` mantidas para clientes mais antigos, e `content` e `blobToken`, que
vêm nulos; o exemplo os omite.
A sua aplicação deve responder com um código de status 2xx. Qualquer outra resposta, ou a falta de resposta, conta como uma notificação com falha.
## Configurando os webhooks
As notificações por webhook são configuradas por organização (*subscription*), no painel de controle da instância do Rest PKI Core
([https://core.pki.rest/](https://core.pki.rest/) na instância SaaS), por um usuário com o papel **Administrador** ou **Gerente** na organização:
1. Autentique-se no painel de controle e clique em **Configurações** no menu lateral
1. No quadro **Configuração de webhook**, na aba **Configuração**, ative **Habilitar webhook**
1. Preencha a **URL** para a qual as requisições HTTP devem ser enviadas
1. Escolha um **Tipo de autenticação**:
* **NoAuth**: nenhuma credencial é enviada
* **Basic**: preencha um **Nome de usuário** e uma **Senha**, enviados no header `Authorization` com o *scheme* `Basic`
* **Bearer**: preencha o **Token de Autenticação**, enviado no header `Authorization` com o *scheme* `Bearer`
* **ApiKey**: preencha o **Token de Autenticação**, enviado no header `X-Api-Key`
1. Clique em **Salvar**
> **Aviso**
>
> Com **Basic**, o Rest PKI Core codifica o nome de usuário e a senha em Base64 separadamente e os une com dois-pontos: o header é
> `Authorization: Basic base64(usuário):base64(senha)`, e não o Base64 de `usuário:senha` descrito na RFC 7617. Um tratamento padrão de autenticação Basic o
> recusa; por isso, verifique o header você mesmo ou escolha **Bearer** ou **ApiKey**.
A aba **Chamadas recentes** lista as notificações enviadas, com o estado de cada uma, a hora da última tentativa e a resposta da sua aplicação.
**Demonstrações e exemplos**
- Receive Rest PKI Core webhooks: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/WebhookRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/WebhookRestCoreController.java) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/webhook-rest-core/index.php)
# [Rest PKI Core *on premises*](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises)
O [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) pode ser utilizado em nuvem (SaaS) no endereço [core.pki.rest](https://core.pki.rest/) ou você pode ter uma instância na sua própria
infraestrutura ("on premises").
## Ambientes suportados
O Rest PKI Core é compatível com os seguintes ambientes:
* [Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/windows-server)
* [Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux)
* [Docker](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/docker)
* [Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure)
Clique em um dos ambientes acima para obter instruções específicas de instalação e atualização.
> **Observação**
>
> As instruções de instalação encontram-se em construção. Se você precisar de instruções ainda indisponíveis, por favor contate-nos.
## Veja também
* [Verificando a versão instalada do Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/check-version)
* [Histórico de versões](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog)
# [Setup do Rest PKI Core em Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux)
O [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) pode ser instalado nas seguintes distribuições de Linux:
* [Ubuntu Server](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/install-ubuntu)
* [Rocky Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/install-rocky)
> **Observação**
>
> Caso necessite suporte a uma distribuição específica, entre em contato conosco.
## Veja também
* [Atualização do Rest PKI Core em Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/troubleshoot)
# [Resolução de problemas do Rest PKI Core (Linux)](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/troubleshoot)
O primeiro passo para diagnosticar eventuais problemas na sua instância do Rest PKI Core é [verificar os logs de sistema](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/troubleshoot/check-logs).
# [Verificando os logs de sistema (Linux)](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/troubleshoot/check-logs)
Quanto instalado em Linux, o [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) escreve seus logs no *systemd journal*, uma solução para gerenciamento de logs centralizada do Linux. Para
visualizar os logs, utiliza-se o utilitário `journalctl`.
Para verificar os logs do Rest PKI Core:
```sh
journalctl -u restpkicore
```
Utilize a flag `-f` para acompanhar o log de maneira contínua (como o comando `tail -f`):
```sh
journalctl -u restpkicore -f
```
Utilize o parâmetro `--since` para visualizar apenas os logs mais recentes:
```sh
journalctl -u restpkicore --since "1 min ago"
```
## Veja também
* [*How To Use Journalctl to View and Manipulate Systemd Logs*](https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs) (artigo da DigitalOcean)
* [Resolução de problemas em Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/troubleshoot)
# [Instalação do Rest PKI Core em Rocky Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/install-rocky)
Para instalar uma instância [on-premises](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises) do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) em Rocky Linux, siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises).
## Pré-requisitos
* Rocky Linux 8.x ou superior
* **Connection string** para um [banco de dados SQL Server ou PostgreSQL criado previamente](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/prepare-database)
* Licença do **PKI SDK** (em formato Base64)
* Licença do **Web PKI** (em formato Base64/binário)
## Instale o ASP.NET Core Runtime 10.0
> **Info**
>
> Estas instruções pressupõem que você está autenticado como **root**. Se não estiver, execute `sudo su -` antes de continuar!
Instale o pacote do ASP.NET Core Runtime, disponível no repositório AppStream:
```sh
dnf install aspnetcore-runtime-10.0
```
Para testar a instalação, execute:
```sh
dotnet --list-runtimes
```
A saída esperada é semelhante a:
```text
Microsoft.AspNetCore.App 10.0.* [*/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.* [*/dotnet/shared/Microsoft.NETCore.App]
```
> **Dica**
>
> Para outras versões do sistema operacional e outras formas de instalar o ASP.NET Core Runtime, veja [esta página](https://learn.microsoft.com/dotnet/core/install/linux)
## Instale o Rest PKI Core
Crie um usuário local para executar a instância do Rest PKI Core:
```sh
mkdir /var/restpkicore
useradd --system --home-dir /var/restpkicore restpkicore
chown restpkicore:restpkicore /var/restpkicore
```
Crie a pasta do site, baixe e extraia os binários:
```sh
mkdir /usr/share/restpkicore
curl -O https://cdn.lacunasoftware.com/restpkicore/restpkicore-5.0.0.tar.gz
tar xzf restpkicore-5.0.0.tar.gz -C /usr/share/restpkicore
chmod -R a=,u+rwX,go+rX /usr/share/restpkicore
```
> **Observação**
>
> Os arquivos do site podem ser lidos por qualquer usuário, mas só podem ser alterados pelo usuário root. Isso significa que o usuário da aplicação (*restpkicore*) pode ler os arquivos, mas não pode alterá-los, o que é intencional.
Crie o arquivo de configuração a partir do template fornecido:
```sh
mkdir /etc/restpkicore
cp /usr/share/restpkicore/config-templates/linux/appsettings.conf /etc/restpkicore/
chown -R root:restpkicore /etc/restpkicore
chmod -R a=,u+rwX,g+rX /etc/restpkicore
```
> **Observação**
>
> Os arquivos de configuração só podem ser lidos por membros do grupo *restpkicore* e só podem ser alterados pelo usuário root. Isso é importante para proteger contra acessos não autorizados os dados sensíveis armazenados nos arquivos de configuração.
## Configure o Rest PKI Core
Edite o arquivo de configuração para configurar sua instância do Rest PKI Core:
```sh
nano /etc/restpkicore/appsettings.conf
```
Na seção `[General]`, para preencher a configuração `EncryptionKey`, gere uma chave de 256 bits para cifrar os dados sensíveis armazenados no banco de dados:
```sh
openssl rand -base64 32
```
Ainda na seção `[General]`, para preencher a configuração `RootPasswordHash`, escolha uma senha forte para o acesso root ao dashboard e calcule o hash dela:
```sh
dotnet /usr/share/restpkicore/Lacuna.RestPki.Site.dll -- hash-root-pass
```
> **Observação**
>
> Se quiser habilitar o gerenciamento de usuários, deixe a configuração `RootPasswordHash` em branco e, em vez disso, siga os passos de [Configuração de OpenID Connect](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-oidc)
Preencha as demais configurações de acordo com as instruções do arquivo de configuração.
## Configure um daemon
Crie o arquivo de definição do serviço:
```sh
touch /etc/systemd/system/restpkicore.service
nano /etc/systemd/system/restpkicore.service
```
Digite o seguinte:
```ini
[Unit]
Description=Rest PKI Core
[Service]
WorkingDirectory=/usr/share/restpkicore
ExecStart=/usr/bin/dotnet Lacuna.RestPki.Site.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=restpkicore
User=restpkicore
Environment=ASPNETCORE_ENVIRONMENT=Linux
Environment=ASPNETCORE_URLS=http://+:5004
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
```
Salve o arquivo, habilite o serviço e inicie-o:
```sh
systemctl enable restpkicore
systemctl start restpkicore
systemctl status restpkicore
```
A saída esperada é semelhante a:
```text
* restpkicore.service - Rest PKI Core
Loaded: loaded (/etc/systemd/system/restpkicore.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2019-07-07 05:50:04 UTC; 4min 22s ago
Main PID: 10960 (dotnet)
Tasks: 31 (limit: 2319)
CGroup: /system.slice/restpkicore.service
└─10960 /usr/bin/dotnet Lacuna.RestPki.Site.dll
...
Dec 04 12:45:08 server.patorum.com restpkicore[32562]: Hosting environment: Production
Dec 04 12:45:08 server.patorum.com restpkicore[32562]: Content root path: /usr/share/restpkicore
Dec 04 12:45:08 server.patorum.com restpkicore[32562]: Now listening on: http://localhost:5004
Dec 04 12:45:08 server.patorum.com restpkicore[32562]: Application started. Press Ctrl+C to shut down.
Hint: Some lines were ellipsized, use -l to show in full.
```
Se necessário, reinicie o serviço: `systemctl restart restpkicore`
Para testar se a instância do Rest PKI Core está em execução, execute:
```sh
curl http://localhost:5004/api/system/info
```
A saída esperada é algo como:
```json
{"productName":"Lacuna Rest PKI Core","productVersion":"1.x.x","spaVersion":"...","timestamp":"..."}
```
## Configure um servidor proxy reverso
> **Observação**
>
> Se preferir usar o Apache em vez do Nginx, [veja este artigo](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-2.2#configure-apache).
Instale o Nginx (se ainda não estiver instalado):
```sh
yum install nginx
systemctl enable nginx.service
systemctl start nginx.service
```
Teste se o Nginx está em execução:
```sh
curl -I http://localhost/
```
Verifique as primeiras linhas da saída, que devem ser semelhantes a:
```text
HTTP/1.1 200 OK
Server: nginx/...
...
```
Edite a configuração do Nginx:
```sh
nano /etc/nginx/nginx.conf
```
Remova ou comente (com `#`) toda a seção `server`, localizada logo abaixo da cláusula `include /etc/nginx/conf.d/*.conf;`. Depois de
comentar a seção, o arquivo de configuração deve ficar semelhante a este:
```nginx
...
http {
...
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
# server {
# listen 80 default_server;
# listen [::]:80 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
...
}
```
Crie um arquivo de configuração de site para o Rest PKI Core:
```sh
touch /etc/nginx/conf.d/restpkicore.conf
nano /etc/nginx/conf.d/restpkicore.conf
```
Digite o seguinte, substituindo o domínio do dashboard no item `server_name` pelo seu próprio domínio:
```nginx
server {
listen 80;
server_name localhost restpkicore.yourcompany.com;
location / {
proxy_pass http://localhost:5004;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
> **Dica**
>
> Idealmente, a configuração do site deve conter as entradas `ssl_certificate` e `ssl_certificate_key` com um certificado SSL válido. Essa configuração está fora do escopo destas instruções.
Permita que o Nginx acesse o serviço do Rest PKI Core:
```sh
setsebool -P httpd_can_network_connect on
```
Teste a configuração do Nginx e recarregue-a:
```sh
nginx -t
nginx -s reload
```
Teste o site:
```sh
curl http://localhost/api/system/info
```
Libere o tráfego HTTP e HTTPS no seu sistema (se ainda não estiver liberado):
```sh
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --zone=public --add-port=443/tcp --permanent
firewall-cmd --reload
```
## Veja também
* [Atualização do Rest PKI Core em Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/troubleshoot)
# [Instalação do Rest PKI Core em Ubuntu Server](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/install-ubuntu)
Para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises) do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) em Ubuntu Server, siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises).
## Pré-requisitos
* Ubuntu Server (qualquer versão ainda suportada pelo fabricante, recomendamos a versão LTS mais recente)
* **Connection string** para um [banco de dados SQL Server ou PostgreSQL criado previamente](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/prepare-database)
* Licença de uso da biblioteca **PKI SDK** (em formato binário/Base64)
* Licença de uso do componente **Web PKI** (em formato binário/Base64)
## Instale o ASP.NET Core Runtime 10.0
> **Info**
>
> Essas instruções assumem que você está autenticado como **root**. Se você não estiver, execute `sudo su -` antes de continuar!
Siga as instruções abaixo dependendo da versão do seu Ubuntu.
### Ubuntu 24.04 (LTS) ou mais recente
O ASP.NET Core Runtime 10.0 está disponível no repositório de pacotes padrão do Ubuntu:
```bash
apt-get update
apt-get install aspnetcore-runtime-10.0
```
### Ubuntu 22.04 (LTS)
Registre o repositório de backports do .NET do Ubuntu e depois instale o runtime:
```bash
apt-get update
apt-get install -y software-properties-common
add-apt-repository -y ppa:dotnet/backports
apt-get update
apt-get install aspnetcore-runtime-10.0
```
> **Aviso**
>
> O Ubuntu 20.04 e versões anteriores não são suportados pelo .NET 10.
### Teste a instalação
Para testar a instalação, execute:
```sh
dotnet --list-runtimes
```
A saída esperada é semelhante a:
```text
Microsoft.AspNetCore.App 10.0.* [*/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.* [*/dotnet/shared/Microsoft.NETCore.App]
```
> **Dica**
>
> Para outras versões do sistema operacional e métodos alternativos de instalação do ASP.NET Core Runtime, visite [esta página](https://learn.microsoft.com/pt-br/dotnet/core/install/linux)
## Instalar o Rest PKI Core
Crie um usuário local para executar o servidor de aplicação do Rest PKI Core:
```sh
mkdir /var/restpkicore
useradd --system --home-dir /var/restpkicore restpkicore
chown restpkicore:restpkicore /var/restpkicore
```
Crie a pasta do site, baixe e extraia os binários:
```sh
mkdir /usr/share/restpkicore
curl -O https://cdn.lacunasoftware.com/restpkicore/restpkicore-5.0.0.tar.gz
tar xzf restpkicore-5.0.0.tar.gz -C /usr/share/restpkicore
chmod -R a=,u+rwX,go+rX /usr/share/restpkicore
```
> **Observação**
>
> Os arquivos do site podem ser lidos por qualquer usuário mas só podem ser alterados por usuários com permissões elevadas. Isso significa que o usuário da aplicação (*restpkicore*)
> pode ler os arquivos mas não pode alterá-los (isso é intencional).
Crie o arquivo de configuração do Rest PKI Core a partir do template fornecido:
```sh
mkdir /etc/restpkicore
cp /usr/share/restpkicore/config-templates/linux/appsettings.conf /etc/restpkicore/
chown -R root:restpkicore /etc/restpkicore
chmod -R a=,u+rwX,g+rX /etc/restpkicore
```
> **Observação**
>
> Arquivos de configuração só podem ser lidos por membros do grupo *restpkicore* e só podem ser alterados por usuários com permissões elevadas. Isso é importante para proteger informações
> sigilosas armazenadas no arquivo de configuração dos demais usuários da máquina.
## Configure o Rest PKI Core
Edite o arquivo de configuração para configurar sua instância do Rest PKI Core:
```sh
nano /etc/restpkicore/appsettings.conf
```
Na seção `[General]`, para preencher o parâmetro `EncryptionKey` gere uma chave de 256 bits para cifrar dados sensíveis armazenados no banco de dados:
```sh
openssl rand -base64 32
```
Ainda na seção `[General]`, para preencher o parâmetro `RootPasswordHash` escolha uma senha forte para o acesso de root e compute o *hash* da senha:
```sh
dotnet /usr/share/restpkicore/Lacuna.RestPki.Site.dll -- hash-root-pass
```
Preencha os demais parâmetros de acordo com as instruções presentes no arquivo de configuração.
## Configurar um *daemon*
Crie o arquivo de definição do serviço:
```sh
touch /etc/systemd/system/restpkicore.service
nano /etc/systemd/system/restpkicore.service
```
Digite o seguinte:
```ini
[Unit]
Description=Rest PKI Core
[Service]
WorkingDirectory=/usr/share/restpkicore
ExecStart=/usr/bin/dotnet Lacuna.RestPki.Site.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=restpkicore
User=restpkicore
Environment=ASPNETCORE_ENVIRONMENT=Linux
Environment=ASPNETCORE_URLS=http://+:5004
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
```
Salve o arquivo, habilite o serviço e inicie-o:
```sh
systemctl enable restpkicore
systemctl start restpkicore
systemctl status restpkicore
```
A saída esperada é semelhante a:
```text
* restpkicore.service - Rest PKI Core
Loaded: loaded (/etc/systemd/system/restpkicore.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2019-07-07 05:50:04 UTC; 4min 22s ago
Main PID: 10960 (dotnet)
Tasks: 31 (limit: 2319)
CGroup: /system.slice/restpkicore.service
└─10960 /usr/bin/dotnet Lacuna.RestPki.Site.dll
...
Dec 04 12:45:08 server.patorum.com restpkicore[32562]: Hosting environment: Production
Dec 04 12:45:08 server.patorum.com restpkicore[32562]: Content root path: /usr/share/restpkicore
Dec 04 12:45:08 server.patorum.com restpkicore[32562]: Now listening on: http://localhost:5004
Dec 04 12:45:08 server.patorum.com restpkicore[32562]: Application started. Press Ctrl+C to shut down.
Hint: Some lines were ellipsized, use -l to show in full.
```
Se necessário, reinicie o serviço: `systemctl restart restpkicore`
Para testar se o servidor do Rest PKI Core está rodando, execute:
```sh
curl http://localhost:5004/api/system/info
```
A saída esperada é algo como:
```json
{"productName":"Lacuna Rest PKI Core","productVersion":"1.x.x","spaVersion":"...","timestamp":"..."}
```
## Configurar um servidor proxy reverso
> **Observação**
>
> Se você preferir usar o Apache ao invés do Nginx, [veja este artigo](https://docs.microsoft.com/pt-br/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-2.2#configure-apache).
Instale o Nginx (se ainda não estiver instalado)
```sh
apt-get install nginx
```
Teste a instalação do Nginx:
```sh
curl -I http://localhost/
```
Verifique as primeiras linhas da saída, que devem ser similares a:
```text
HTTP/1.1 200 OK
Server: nginx/...
...
```
Desabilite o site padrão do Nginx:
```sh
rm /etc/nginx/sites-enabled/default
```
Crie um arquivo de configuração para o site do Rest PKI Core:
```sh
touch /etc/nginx/sites-available/restpkicore
nano /etc/nginx/sites-available/restpkicore
```
Digite o seguinte, substituindo o valor do item `server_name` pelo seu domínio:
```nginx
server {
listen 80;
server_name localhost restpkicore.yourcompany.com;
location / {
proxy_pass http://localhost:5004;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
> **Dica**
>
> Idealmente, a configuração do site deve conter as entradas `ssl_certificate` e `ssl_certificate_key` com o certificado SSL válido. Essa configuração está fora do escopo dessas
> instruções.
Ative o site:
```sh
ln -sf /etc/nginx/sites-available/restpkicore /etc/nginx/sites-enabled/restpkicore
```
Teste a configuração do Nginx e recarregue-a:
```sh
nginx -t
nginx -s reload
```
Teste o site:
```sh
curl http://localhost/api/system/info
```
## Veja também
* [Atualização do Rest PKI Core em Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/troubleshoot)
# [Atualização do Rest PKI Core em Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/update)
Para atualizar sua [instância *on premises*](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises) do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core), siga os passos abaixo:
> **Info**
>
> Essas instruções assumem que você está autenticado como **root**. Se você não estiver, execute `sudo su -` antes de continuar!
```sh
curl -O https://cdn.lacunasoftware.com/restpkicore/restpkicore-5.0.0.tar.gz
systemctl stop restpkicore
rm -fR /usr/share/restpkicore/*
tar xzf restpkicore-5.0.0.tar.gz -C /usr/share/restpkicore
chmod -R a=,u+rwX,go+rX /usr/share/restpkicore
systemctl start restpkicore
```
## Veja também
* [Setup do Rest PKI Core em Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/troubleshoot)
# [Rest PKI Core - Utilitário de linha de comando](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool)
As [instâncias on-premises](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises) do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) disponibilizam um utilitário de linha de comando que pode ser
usado para realizar determinadas tarefas administrativas.
## Executando um comando
Abra um terminal e navegue até o diretório de instalação, que depende da plataforma de instalação
(o diretório pode ser diferente se você escolheu um diretório personalizado durante a instalação):
* Windows Server: `cd C:\inetpub\RestPkiCore`
* Linux: `cd /usr/share/restpkicore`
Em seguida, execute:
```sh
dotnet Lacuna.RestPki.Site.dll [settings] -- command [command-options]
```
Ou, no Docker, basta executar o seguinte em qualquer diretório:
```sh
docker run -i lacunasoftware/restpkicore:5.0 [settings] -- command [command-options]
```
Os argumentos `settings` são configurações adicionais que sobrescrevem as configurações dos arquivos de configuração e das variáveis de ambiente,
por exemplo `--Section1:Name1=value1 --Section2:Name2=value2`.
Os argumentos `command-options` dependem de cada comando; consulte a documentação específica do comando.
Comandos disponíveis:
* [gen-enc-key](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool/gen-enc-key)
* [hash-root-pass](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool/hash-root-pass)
* [update-db](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool/update-db)
* [test-email](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool/test-email)
## Veja também
* [Rest PKI Core on-premises](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises)
# [Comando do Rest PKI Core: **gen-enc-key**](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool/gen-enc-key)
## Nome
`gen-enc-key` - Gera uma chave de criptografia aleatória (para criptografar colunas sensíveis do banco de dados)
## Sintaxe
```sh
dotnet Lacuna.RestPki.Site.dll -- gen-enc-key
```
Ou, no Docker:
```sh
docker run lacunasoftware/restpkicore:5.0 -- gen-enc-key
```
## Descrição
O comando `gen-enc-key` gera uma chave de criptografia de 256 bits para ser usada na configuração `General__EncryptionKey`, que o Rest PKI Core usa para criptografar os dados sensíveis
armazenados no banco de dados.
## Veja também
* [Utilitário de linha de comando do Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool)
# [Comando do Rest PKI Core: **hash-root-pass**](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool/hash-root-pass)
## Nome
`hash-root-pass` - Calcula um hash com salt para uma senha root informada
## Sintaxe
```sh
dotnet Lacuna.RestPki.Site.dll -- hash-root-pass
```
Ou, no Docker:
```sh
docker run -i lacunasoftware/restpkicore:5.0 -- hash-root-pass
```
## Descrição
O comando `hash-root-pass` deve ser usado com o *modo de autenticação root*. Use-o para calcular um hash com salt da senha root desejada
e, em seguida, defina o hash na configuração `General__RootPasswordHash`.
A senha é lida do `stdin`.
## Veja também
* [Utilitário de linha de comando do Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool)
# [Comando do Rest PKI Core: **test-email**](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool/test-email)
## Nome
`test-email` - Testa o envio de e-mails
## Sintaxe
```sh
dotnet Lacuna.RestPki.Site.dll [settings] -- test-email
```
Ou, no Docker:
```sh
docker run -i lacunasoftware/restpkicore:5.0 [settings] -- test-email
```
Os argumentos `settings` são configurações adicionais que sobrescrevem as configurações dos arquivos de configuração e das variáveis de ambiente,
por exemplo `--Section1:Name1=value1 --Section2:Name2=value2`.
## Descrição
O comando `test-email` testa a configuração de e-mail tentando enviar uma mensagem de teste para o endereço informado.
## Veja também
* [Utilitário de linha de comando do Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool)
# [Comando do Rest PKI Core: **update-db**](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool/update-db)
## Nome
`update-db` - Atualiza o modelo do banco de dados
## Sintaxe
```sh
dotnet Lacuna.RestPki.Site.dll [settings] -- update-db
```
Ou, no Docker:
```sh
docker run -i lacunasoftware/restpkicore:5.0 [settings] -- update-db
```
Os argumentos `settings` são configurações adicionais que sobrescrevem as configurações dos arquivos de configuração e das variáveis de ambiente,
por exemplo `--Section1:Name1=value1 --Section2:Name2=value2`.
## Descrição
O comando `update-db` atualiza o modelo do banco de dados aplicando uma série de *migrações*. Ele deve ser usado apenas quando
[o Rest PKI Core é executado sem privilégios db_owner](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/unprivileged-db-user).
Como, nesse cenário, as credenciais de banco de dados do Rest PKI Core não concedem permissão para alterar o modelo do banco, normalmente
você vai executar este comando passando uma **connection string privilegiada**:
```sh
dotnet Lacuna.RestPki.Site.dll --ConnectionStrings:DefaultConnection="Data Source=SERVER;Initial Catalog=DATABASE;User ID=USERNAME;Password=PASSWORD" -- update-db
```
> **Dica**
>
> No Linux, se a sua senha contiver o caractere `!`, use aspas simples, por exemplo `--ConnectionStrings:DefaultConnection='...'`, para evitar
> expansões do histórico do shell
## Veja também
* [Executando o Rest PKI Core sem privilégios db_owner](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/unprivileged-db-user)
* [Utilitário de linha de comando do Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool)
# [Atualizando o Rest PKI Core da versão 2.x para 3.0](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/update-30)
Como o ASP.NET Core 6.0 foi descontinuado, a versão [3.0](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog#v3-0-0) do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) foi migrada para o ASP.NET Core 8.0, que é
a versão LTS mais recente do ASP.NET Core, [com suporte da Microsoft até novembro de 2026](https://dotnet.microsoft.com/platform/support/policy/dotnet-core#lifecycle).
Por isso, antes de atualizar a sua instância [on-premises](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises), você precisa seguir alguns passos adicionais.
## 1. Instale o ASP.NET Core Runtime 8.0
Consulte a seção *Instale o ASP.NET Core Runtime 8.0* do procedimento de instalação correspondente à sua plataforma:
* Linux
* [Ubuntu Server](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/install-ubuntu#install-aspnet-core)
* [Rocky Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/install-rocky#instale-o-aspnet-core-runtime-100)
> **Dica**
>
> Não é necessário desinstalar as versões anteriores do ASP.NET Core Runtime: várias versões podem coexistir no mesmo sistema sem problemas
> **Observação**
>
> No Docker, este passo não é necessário, pois a nova imagem 3.x já vem com o ASP.NET Core Runtime 8.0 embutido
## 2. Defina um AppDiscriminator explícito
Verifique se o seu arquivo de configuração contém uma configuração chamada `AppDiscriminator` na seção `General` (ou `General__AppDiscriminator`, se você usa variáveis de ambiente).
Se essa configuração existir e estiver preenchida, pule para o próximo passo. Se ela não existir (ou estiver com valor vazio), siga as instruções abaixo.
Abra o log da aplicação e procure a seguinte entrada:
```text
Application starting: Lacuna Rest PKI Core (version: *, spaVersion: *, appDiscriminator: 'YOUR_APP_DISCRIMINATOR')
^^^^^^^^^^^^^^^^^^^^^^
```
Copie o valor do campo *appDiscriminator* **sem as aspas simples ao redor** (no exemplo acima, o valor correto seria `YOUR_APP_DISCRIMINATOR`,
e não `'YOUR_APP_DISCRIMINATOR'`) e adicione a configuração `AppDiscriminator`, com esse valor, na seção `General` do seu arquivo de configuração.
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[General]
AppDiscriminator=YOUR_APP_DISCRIMINATOR
```
Exemplo (variáveis de ambiente):
```sh
General__AppDiscriminator=YOUR_APP_DISCRIMINATOR
```
Em seguida, reinicie a aplicação e verifique os logs novamente. Deve ser registrado um novo log "application starting", sem alteração no campo *appDiscriminator*.
## 3. Atualize o Rest PKI Core
Depois de seguir os passos acima, prossiga com as instruções de atualização.
## Solução de problemas com erros de certificado no SQL Server
Diferentemente do .NET 6, no .NET 8 o padrão é validar o certificado do SQL Server. Isso pode fazer com que a sua instância falhe
ao iniciar, com o seguinte erro nos logs:
> Microsoft.Data.SqlClient.SqlException: 'A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.)'
Nesse caso, certifique-se de que o seu SQL Server está configurado com um certificado TLS confiável e que ele não expirou.
Se preferir ignorar a validação do certificado do SQL Server, acrescente `;TrustServerCertificate=True` à connection string do banco de dados no arquivo de configuração.
## Azure App Services
Se a sua instância do Rest PKI Core estiver hospedada no Azure App Services:
1. No portal do Azure, acesse a configuração do seu App Service
1. Clique em **Configuration** e selecione a aba **General settings**
1. Na seção *Stack settings*:
1. A configuração *Stack* deve estar definida como **.NET**
1. A configuração *.NET version* deve estar definida como **.NET 8 (LTS)**
> **Dica**
>
> A seção *Stack settings* mencionada acima só aparece se o App Service Plan for baseado em Windows. Nos planos baseados em Linux, nenhuma alteração é necessária.
# [Atualizando o Rest PKI Core da versão 3.x para 4.0](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/update-40)
## 1. Decida se você precisa criar índices manualmente
Se a sua instância tem um **grande número de sessões de assinatura (Signature Sessions) ou transações (milhões)**, recomenda-se criar manualmente alguns dos índices exigidos pela versão 4.0 **antes de atualizar**, seguindo o passo 2.
Se a sua instância **não se enquadra nessas condições**, você pode pular o passo 2 e **seguir direto para o passo 3**.
## 2. Crie os índices manualmente
### Usando SQL Server
Execute os seguintes comandos no seu banco de dados SQL Server, um de cada vez, garantindo que cada comando termine com sucesso antes de executar o próximo.
```SQL
IF NOT EXISTS (
SELECT 1
FROM sys.columns
WHERE [name] = 'DateSignaturesStartedUtc'
AND [object_id] = OBJECT_ID('SignatureSessions')
)
BEGIN
ALTER TABLE [SignatureSessions]
ADD [DateSignaturesStartedUtc] datetime2 NULL;
END
IF NOT EXISTS (
SELECT 1 FROM sys.indexes
WHERE name = 'IX_SignatureSessions_DateCompletedUtc_DateSignaturesStartedUtc'
AND object_id = OBJECT_ID('dbo.SignatureSessions')
)
CREATE INDEX [IX_SignatureSessions_DateCompletedUtc_DateSignaturesStartedUtc]
ON [SignatureSessions] ([DateCompletedUtc], [DateSignaturesStartedUtc]);
IF NOT EXISTS (
SELECT 1 FROM sys.indexes
WHERE name = 'IX_SignatureSessions_DateCreatedUtc_DateCompletedUtc'
AND object_id = OBJECT_ID('dbo.SignatureSessions')
)
CREATE INDEX [IX_SignatureSessions_DateCreatedUtc_DateCompletedUtc]
ON [SignatureSessions] ([DateCreatedUtc], [DateCompletedUtc]);
IF NOT EXISTS (
SELECT 1 FROM sys.indexes
WHERE name = 'IX_SignatureSessions_StatusCode_DateCreatedUtc'
AND object_id = OBJECT_ID('dbo.SignatureSessions')
)
CREATE INDEX [IX_SignatureSessions_StatusCode_DateCreatedUtc]
ON [SignatureSessions] ([StatusCode], [DateCreatedUtc]);
IF NOT EXISTS (
SELECT 1 FROM sys.indexes
WHERE name = 'IX_SignatureSessions_SubscriptionId_DateCreatedUtc'
AND object_id = OBJECT_ID('dbo.SignatureSessions')
)
CREATE INDEX [IX_SignatureSessions_SubscriptionId_DateCreatedUtc]
ON [SignatureSessions] ([SubscriptionId], [DateCreatedUtc]);
IF NOT EXISTS (
SELECT 1 FROM sys.indexes
WHERE name = 'IX_SignatureSessions_SubscriptionId_StatusCode_DateCreatedUtc'
AND object_id = OBJECT_ID('dbo.SignatureSessions')
)
CREATE INDEX [IX_SignatureSessions_SubscriptionId_StatusCode_DateCreatedUtc]
ON [SignatureSessions] ([SubscriptionId], [StatusCode], [DateCreatedUtc]);
IF NOT EXISTS (
SELECT 1 FROM sys.indexes
WHERE name = 'IX_Documents_DateCreatedUtc'
AND object_id = OBJECT_ID('dbo.Documents')
)
CREATE INDEX [IX_Documents_DateCreatedUtc]
ON [Documents] ([DateCreatedUtc]);
IF NOT EXISTS (
SELECT 1 FROM sys.indexes
WHERE name = 'IX_Documents_SubscriptionId_DateCreatedUtc'
AND object_id = OBJECT_ID('dbo.Documents')
)
CREATE INDEX [IX_Documents_SubscriptionId_DateCreatedUtc]
ON [Documents] ([SubscriptionId], [DateCreatedUtc]);
```
### Usando PostgreSQL
Execute os seguintes comandos no seu banco de dados PostgreSQL, um de cada vez, garantindo que cada comando termine com sucesso antes de executar o próximo.
```SQL
ALTER TABLE "SignatureSessions" ADD COLUMN IF NOT EXISTS "DateSignaturesStartedUtc" timestamptz DEFAULT NULL;
CREATE INDEX IF NOT EXISTS "IX_SignatureSessions_DateCompletedUtc_DateSignaturesStartedUtc" ON "SignatureSessions" ("DateCompletedUtc", "DateSignaturesStartedUtc");
CREATE INDEX IF NOT EXISTS "IX_SignatureSessions_DateCreatedUtc_DateCompletedUtc" ON "SignatureSessions" ("DateCreatedUtc", "DateCompletedUtc");
CREATE INDEX IF NOT EXISTS "IX_SignatureSessions_StatusCode_DateCreatedUtc" ON "SignatureSessions" ("StatusCode", "DateCreatedUtc");
CREATE INDEX IF NOT EXISTS "IX_SignatureSessions_SubscriptionId_DateCreatedUtc" ON "SignatureSessions" ("SubscriptionId", "DateCreatedUtc");
CREATE INDEX IF NOT EXISTS "IX_SignatureSessions_SubscriptionId_StatusCode_DateCreatedUtc" ON "SignatureSessions" ("SubscriptionId", "StatusCode", "DateCreatedUtc");
CREATE INDEX IF NOT EXISTS "IX_Documents_DateCreatedUtc" ON "Documents" ("DateCreatedUtc");
CREATE INDEX IF NOT EXISTS "IX_Documents_SubscriptionId_DateCreatedUtc" ON "Documents" ("SubscriptionId", "DateCreatedUtc");
```
## 3. Atualize o Rest PKI Core
Prossiga com as instruções de atualização padrão:
- [Docker](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/docker): atualize para a imagem `lacunasoftware/restpkicore:4.1`.
- [Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/update)
# [Atualizando o Rest PKI Core da versão 4.x para 5.0](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/update-50)
A versão [5.0](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog#v5-0-0) do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) foi migrada do ASP.NET Core 8.0 para o ASP.NET Core 10.0, a versão LTS atual
do ASP.NET Core, [com suporte da Microsoft até novembro de 2028](https://dotnet.microsoft.com/platform/support/policy/dotnet-core#lifecycle) (o suporte
ao ASP.NET Core 8.0 termina em novembro de 2026).
Por isso, antes de atualizar a sua instância [on-premises](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises), você pode precisar seguir alguns passos adicionais, dependendo de como ela
está instalada.
## 1. Verifique como a sua instância está instalada
- Se a sua instância roda em **Docker** ou no **Azure App Services**, a imagem 5.x já inclui o ASP.NET Core Runtime 10.0 e tudo o mais de que ela
precisa. Não há nada para instalar: **pule direto para o passo 3**.
- Se a sua instância roda diretamente em **Linux** (Ubuntu, Rocky Linux) ou em **Windows Server**, você precisa instalar o novo runtime antes:
**siga para o passo 2**.
## 2. Instale o ASP.NET Core Runtime 10.0
Instale o runtime correspondente à sua plataforma, conforme descrito abaixo.
> **Dica**
>
> Não é necessário desinstalar as versões anteriores do ASP.NET Core Runtime: várias versões podem coexistir no mesmo sistema sem problemas
### Ubuntu
> **Info**
>
> Estas instruções assumem que você está logado como **root**. Se não estiver, execute `sudo su -` antes de continuar!
No **Ubuntu 24.04 (LTS) ou mais recente**, o ASP.NET Core Runtime 10.0 está disponível no repositório de pacotes padrão do Ubuntu:
```sh
apt update
apt install -y aspnetcore-runtime-10.0
```
No **Ubuntu 22.04 (LTS)**, primeiro registre o repositório de backports do .NET do Ubuntu e depois instale o runtime:
```sh
apt update
apt install -y software-properties-common
add-apt-repository -y ppa:dotnet/backports
apt update
apt install -y aspnetcore-runtime-10.0
```
> **Aviso**
>
> O Ubuntu 20.04 e versões anteriores não são suportados pelo .NET 10. Atualize o sistema operacional antes de atualizar o Rest PKI Core.
> **Cuidado**
>
> Se o ASP.NET Core Runtime anterior foi instalado a partir do repositório de pacotes da Microsoft (`packages.microsoft.com`), como era o caso no Ubuntu 20.04 e anteriores, remova esses pacotes antes de instalar o runtime pelo repositório do Ubuntu. Misturar pacotes do .NET dos dois repositórios causa erros na inicialização da aplicação. Para mais informações, veja [.NET package mix ups on Linux](https://learn.microsoft.com/dotnet/core/install/linux-package-mixup).
### Rocky Linux
> **Info**
>
> Estas instruções assumem que você está logado como **root**. Se não estiver, execute `sudo su -` antes de continuar!
No Rocky Linux 8, 9 e 10 (assim como no RHEL e em outras distribuições compatíveis), o ASP.NET Core Runtime 10.0 está disponível no repositório AppStream:
```sh
dnf install aspnetcore-runtime-10.0
```
### Windows Server
Baixe e instale o **Hosting Bundle do ASP.NET Core Runtime 10.0** na [página de download do .NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0)
(seção *ASP.NET Core Runtime*, item *Hosting Bundle* em *Windows*). Após a instalação, reinicie o IIS:
```powershell
iisreset
```
### Teste a instalação
No Linux, execute:
```sh
dotnet --list-runtimes
```
A saída deve incluir uma linha semelhante a:
```text
Microsoft.AspNetCore.App 10.0.* [*/dotnet/shared/Microsoft.AspNetCore.App]
```
> **Dica**
>
> Para outras versões de sistema operacional e formas alternativas de instalar o ASP.NET Core Runtime, veja [esta página](https://learn.microsoft.com/dotnet/core/install/)
## 3. Atualize o Rest PKI Core
Siga as instruções padrão de atualização da sua plataforma.
> **Observação**
>
> A versão 5.0 atualiza o modelo do banco de dados. Na instalação padrão, em que a aplicação tem privilégios de owner sobre o banco, o modelo é
> atualizado automaticamente na primeira vez em que a versão 5.0 é iniciada. Se a sua instância roda [sem privilégios de db_owner](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/unprivileged-db-user),
> execute o comando [update-db](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool/update-db) antes de iniciar a nova versão.
### Docker
Atualize os seus containers para a imagem `lacunasoftware/restpkicore:5.0`, conforme descrito na [instalação em Docker](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/docker). Nenhuma outra
mudança é necessária: a imagem já inclui o ASP.NET Core Runtime 10.0 e todas as suas dependências.
As imagens Linux da versão 5.x são baseadas no **Ubuntu 24.04 (noble)** em vez do Debian 12 (bookworm). Isso não tem efeito se você apenas executa a
imagem.
### Azure App Services
Siga as [instruções de atualização no Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure/update) com a imagem `5.0.0`. A observação acima sobre a imagem Docker também
vale aqui.
### Linux
Siga as [instruções de atualização no Linux](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/linux/update) com o pacote `restpkicore-5.0.0.tar.gz`.
### Windows Server
Baixe o pacote `restpkicore-5.0.0.zip` indicado na [configuração no Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/windows-server), pare o site no IIS, substitua os arquivos
da pasta do site pelos do novo pacote e inicie o site novamente.
## 4. Verifique a versão instalada
Após a atualização, [verifique a versão](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/check-version) da sua instância. O campo `productVersion` deve começar com `5.0`.
## OpenTelemetry
Se a sua instância exporta telemetria para um collector OpenTelemetry, observe que o valor padrão do atributo de recurso `service.name` mudou de
`restpkicore` para `Lacuna Rest PKI Core`. Se os seus dashboards ou consultas filtram pelo valor anterior, defina a variável de ambiente
`OTEL_SERVICE_NAME` explicitamente:
```sh
OTEL_SERVICE_NAME=restpkicore
```
# [Rest PKI Core - Setup em Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure)
## Veja também
* [Instalação do Rest PKI Core em Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure/install)
* [Atualização do Rest PKI Core em Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure/update)
# [Instalação do Rest PKI Core em Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure/install)
Para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises) do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) em um [Azure App Service](https://docs.microsoft.com/azure/app-service/overview),
siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises#platforms).
> **Dica**
>
> Para permitir o uso do recurso de certificados SSL gratuitos pelo *App Service Managed Certificates*, sugerimos não escolher "naked domains", ou seja,
> domínios sem porção de subdomínio, por exemplo ~~patorum.com~~. Prefira, por exemplo, `restpki.patorum.com`.
## Preparação
As instruções a seguir assumem que você já tem instalado localmente o [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) e já possui
os seguintes recursos criados na sua conta do Azure:
* Um *SQL Server* (a criação do *SQL database* é coberta por este artigo)
* Um *App Service Plan* com sistema operacional **Linux** (a criação do App Service é coberta por este artigo)
* Uma zona de DNS referente ao domínio de acesso ao Rest PKI Core
Durante a instalação, serão criados alguns recursos:
* Um *Container Registry*
* Um banco de dados (*SQL database*)
* Uma *storage account*
* Um App Service
Sugerimos criar um **resource group** para agrupar os recursos criados. Entretanto, essa é uma medida com propósito meramente de organização. O que
realmente é importante é que **todos os recursos sejam criados na mesma região**. Isso é fundamental para o funcionamento adequado do sistema.
> **Observação**
>
> Os passos abaixo descrevem a criação mais básica de cada recurso. Dependendo da sua infraestrutura você pode querer tomar cuidados
> adicionais de segurança ou resiliência, como por exemplo restringir o acesso a uma rede privada.
## Criação de um *Container registry*
Siga os procedimentos abaixo para criar um *container registry* para armazenar localmente uma cópia das imagens de Docker do sistema:
1. Na opção **Container registries**, clique em **+ Create**
1. Na primeira aba (*Basics*)
* Preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
* Tome nota do *registry name* escolhido
* Em *SKU*, escolha **Basic**
1. Clique em **Review + create**, em seguida em **Create**
Uma vez concluída a criação do *container registry*, replique a imagem de Docker do sistema para ele (substitua `MY_ACR_NAME` pelo nome escolhido
no passo anterior):
```sh
az login
```
> **Observação**
>
> Caso você tenha acesso a mais de uma conta no Azure, pode ser necessário passar o argumento `--tenant YOUR_TENANT`
```sh
az acr login --name MY_ACR_NAME
az acr import --name MY_ACR_NAME --source docker.io/lacunasoftware/restpkicore:5.0.0 --image restpkicore:5.0.0
```
> **Dica**
>
> Substitua `5.0.0` pela versão atualmente recomendada (veja a [instalação em Docker](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/docker))
## Criação do banco de dados
Siga os procedimentos abaixo para criar um banco de dados para o sistema (você precisa ter um *SQL Server* previamente criado):
1. Na opção **SQL databases**, clique em **+ Create**
1. Na primeira aba (*Basics*), preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
1. Clique na aba **Additional settings**
1. Em **Collation** preencha `Latin1_General_100_CI_AI`
1. Clique em **Review + create**
1. Clique em **Create**
> **Aviso**
>
> A *collation* do banco de dados **PRECISA SER** `Latin1_General_100_CI_AI`. Criar o banco de dados com uma *collation* diferente provavelmente fará com que a instalação falhe!
Uma vez concluída a criação do banco de dados, clique em **Go to resource**. Em seguida, obtenha a *connection string*:
1. Clique em **Connection strings**
1. Tome nota da connection string exibida na seção **ADO.NET (SQL authentication)**
> **Observação**
>
> Substitua na connection string copiada o trecho `{your_password}` pela senha do banco de dados
## Criação de uma *storage account*
Precisamos de uma *storage account* para armazenar arquivos (o sistema armazena arquivos fora do banco de dados para mantê-lo organizado) e também os
logs de sistema. Siga os passos abaixo para criar uma *storage account* (caso queira utilizar uma conta existente, pule essa parte):
1. Em **Storage accounts**, clique em **+ Create**
1. Na primeira aba (*Basics*), preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
* Em *Performance*, escolha **Standard**
* Em *Redundancy*, escolha a opção que preferir (sugerimos a opção **Zone-redundant storage (ZRS)** -- [clique aqui](https://azure.microsoft.com/documentation/articles/storage-redundancy/)
para detalhes sobre as diferentes opções de replicação)
1. Clique em **Review**
1. Clique em **Create**
Uma vez concluída a criação da *storage account*, clique em **Go to resource**. Em seguida, obtenha uma *connection string*:
1. Nas configurações da *storage account*, clique em **Access keys**
1. Na seção **key1**, tome nota do valor do campo **Connection string**
## Criação do App Service
Siga os procedimentos abaixo para criar um *App Service* (você precisa ter um *App Service Plan* previamente criado com sistema operacional **Linux**):
1. Em **App Services**, clique em **+ Create** em seguida em **+ Web App**
1. Na primeira aba (*Basics*), preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
* Em *Publish*, escolha **Docker Container**
* Em *Operating System*, escolha **Linux**
* Em *Region*, escolha a região onde está o seu *app service plan*
* Em *Linux Plan*, escolha o seu *app service plan*
* Clique em **Next : Docker >**
1. Na aba seguinte (*Docker*), preencha:
* Em *Options*, deixe a opção **Single Container**
* Em *Image Source*, selecione **Azure Container Registry**
* Em *Registry*, selecione o *container registry* criado anteriormente
* Em *Image* e *Tag*, selecione a imagem/tag do sistema conforme o comando `az acr import` realizado anteriormente
* Deixe a opção *Startup Command* **em branco**
* Clique em **Review + create**
1. Clique em **Create**
Uma vez concluída a criação do App Service, clique em **Go to resource**. Em seguida, tome nota de seu domínio, por exemplo `meu-app-service.azurewebsites.net`.
Vá em **Configuration** do App Service e adicione as seguintes configurações:
* `ASPNETCORE_ENVIRONMENT`: `Azure`
* `Bindings__HttpsMode`: `Strict`
* `STANDBY`: `True`
Salve as configurações.
Ainda na configuração do App Service, vá na aba **General settings** e, na opção **HTTPS Only**, marque **Off** (a própria aplicação se encarregará de exigir acesso via HTTPS da maneira mais adequada).
Em seguida, vá em **Custom domains** e copie o **Custom Domain Verification ID** (esse valor será necessário mais à frente).
## Configuração do domínio
Crie o registro de DNS para o domínio de acesso ao Rest PKI Core:
> **Observação**
>
> Nas instruções abaixo, utilizaremos como exemplo a criação do apontamento para o domínio `app.patorum.com`
1. Em **DNS zones**, clique na zona à qual o domínio pertence (ex: `patorum.com`)
1. Clique em **+ Record set**
1. Em *Name*, digite a porção mais à esquerda do domínio (ex: `app`)
1. Em *Type*, selecione **CNAME**
1. Em *Alias record set*, selecione **No**
1. Em *Alias*, preencha o domínio do App Service (ex: `meu-app-service.azurewebsites.net`)
1. Clique em **OK**
1. Clique novamente em **+ Record set**
1. Em *Name*, digite `asuid.` seguido da porção mais à esquerda do domínio (ex: `asuid.app`)
1. Em *Type*, selecione **TXT**
1. Em *Value*, cole o valor do **Custom Domain Verification ID** anteriormente copiado do App Service
1. Clique em **OK**
> **Observação**
>
> Caso você não utilize o Azure como servidor de DNS das zonas, realize o procedimento equivalente no seu servidor de DNS
Após criar o apontamento, adicione o domínio ao *App Service*:
1. De volta às configurações do App Service, clique em **Custom domains**
1. Clique em **+ Add custom domain**
1. Selecione **All other domain services**
1. Deixe selecionadas as opções **App Service Managed Certificate** e **SNI SSL**
1. Em *Domain*, preencha o domínio (ex: `app.patorum.com`)
1. Clique em **Validate**
1. Após a validação, clique em **Add**
## Configuração do Rest PKI Core
No menu lateral do App Service, na seção *Development Tools*, clique em **SSH**, em seguida em **Go →**. Você será levado a um terminal. Navegue
para a pasta `/app`:
```bash
cd /app
```
> **Dica**
>
> Embora o terminal pareça iniciar na pasta `/app`, o comando acima **é necessário** devido a um bug do Azure
Execute o comando abaixo para gerar a chave criptográfica utilizada para cifrar valores sensíveis no banco de dados:
```cmd
dotnet Lacuna.RestPki.Site.dll -- gen-enc-key
```
Tome nota do valor gerado.
Escolha uma senha forte para proteger o acesso de *root* ao painel de controle, e calcule o hash dessa senha com o comando abaixo:
```cmd
dotnet Lacuna.RestPki.Site.dll -- hash-root-pass
```
Novamente, tome nota do valor gerado.
Feche o terminal, voltando ao portal do Azure. No App Service, vá em **Configuration** e adicione as seguintes configurações:
* `General__AppDiscriminator`: veja a seção [App discriminator](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure/install#app-discriminator) abaixo
* `General__EncryptionKey`: chave criptográfica gerada acima
* `General__RootPasswordHash`: hash da senha de *root* calculado acima
* `General__SiteUrl`: URL pública do site (ex: `https://restpki.patorum.com/`)
* `General__SiteName` (opcional): nome da sua instância do Rest PKI Core (o padrão é *Rest PKI Core*)
* `Oidc__Enabled`: `False` (desabilita a [integração com OpenID Connect](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-oidc), por ora)
> **Observação**
>
> Caso queira habilitar o gerenciamento de usuários via OpenID Connect, deixe a configuração `General__RootPasswordHash` em branco e siga os passos em
> [Configuração de OpenID Connect](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-oidc).
Adicione, também, as configurações descritas nas seções a seguir.
### App discriminator
A configuração `General__AppDiscriminator` identifica a instância e é utilizada para derivar as chaves de proteção de dados do sistema.
> **Aviso**
>
> Esta configuração é **obrigatória** no Azure App Services. Caso não seja definida explicitamente, um valor é derivado automaticamente e pode
> **mudar quando o container é recriado**, o que impede a decifragem dos dados sensíveis já armazenados, causando **perda de dados**. Escolha um valor
> estável (por exemplo `RestPkiCore`) e **nunca o altere** após a instalação. Para mais detalhes, veja a seção *Defina um AppDiscriminator explícito* do artigo
> [Atualização da versão 2.x para 3.0](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/update-30).
* `General__AppDiscriminator`: um valor estável que identifica a instância, ex: `RestPkiCore`
### PKI Suite
Configurações do PKI Suite:
* `PkiSuite__SdkLicense`: sua licença para o PKI SDK, no formato Base64 (**obrigatório**)
* `PkiSuite__WebLicense`: sua licença para o componente Web PKI no formato binário (Base64). Somente obrigatório se os usuários forem assinar documentos com certificados em seus computadores (assinatura no navegador via Web PKI)
### Blob Storage
Configuração da *storage account* (o tipo de armazenamento e o nome do container já vêm pré-configurados para o ambiente Azure):
* `BlobStorage__ConnectionString`: *connection string* da *storage account* criada anteriormente
* `BlobStorage__ContainerName` (opcional): nome do *container* a ser utilizado para armazenar arquivos. Caso omitido, um container denominado *restpki* é utilizado.
### Logging
Configuração de log (o *sink* de logs para o Azure Table já vem pré-configurado para o ambiente Azure):
* `Serilog__WriteTo__0__Args__connectionString`: *connection string* da *storage account* criada anteriormente
* `Serilog__WriteTo__0__Args__storageTableName` (opcional): nome da tabela a ser utilizada para armazenar os logs. Caso omitido, uma tabela denominada *RestPkiCoreLog* é utilizada.
### Connection string
Na seção *Connection strings* (final da página de configurações), clique em **+ New connection string** e preencha:
* **Name**: `DefaultConnection`
* **Value**: valor da connection string obtido durante a criação do banco de dados
* **Type**: escolha **SQLAzure**
> **Dica**
>
> O Rest PKI Core também é compatível com PostgreSQL. Caso deseje utilizar um banco de dados Azure Database for PostgreSQL, entre em contato com o suporte
> para obter mais informações. Para detalhes sobre os bancos de dados suportados, veja [Preparando um banco de dados](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/prepare-database).
Salve as configurações feitas até o momento clicando em **Save**.
> **Observação**
>
> Sempre que a documentação mencionar algo como "na seção **Sec**, atribua a configuração **Conf** ao valor ...", no Azure App Services você deve
> compor o nome da configuração com os nomes da seção e da configuração separados por `__` (**dois** *underscores*), ou seja, no exemplo acima: `Sec__Conf`
## Iniciando o App Service
Por fim, remova a configuração `STANDBY` do App Service e salve as configurações. Em seguida, acesse a URL pública do site (o primeiro acesso pode demorar alguns instantes).
Autentique-se com a senha de *root* escolhida durante a configuração. Você então terá acesso ao painel de controle.
Para começar a utilizar o sistema, crie uma [organização](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/create-sub) e adicione um usuário administrador a ela.
## Veja também
* [Criação de organização no Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/create-sub)
* [Atualização do Rest PKI Core em Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure/update)
# [Atualização do Rest PKI Core em Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure/update)
Antes de atualizar, verifique se a nova versão exige passos adicionais de migração:
* [Atualização da versão 2.x para 3.0](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/update-30)
* [Atualização da versão 3.x para 4.0](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/update-40)
* [Atualização de 4.x para 5.0](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/update-50)
Em seguida, replique a nova imagem de Docker do sistema para o seu *container registry* (substitua `MY_ACR_NAME` pelo nome escolhido durante a instalação
e `5.0.0` pela versão desejada):
```sh
az login
az acr login --name MY_ACR_NAME
az acr import --name MY_ACR_NAME --source docker.io/lacunasoftware/restpkicore:5.0.0 --image restpkicore:5.0.0
```
Por fim, no App Service do sistema, vá em **Deployment Center** e, no campo **Tag**, selecione a nova versão e clique em **Save**.
## Veja também
* [Instalação do Rest PKI Core em Azure App Services](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/azure/install)
# [Verificando a versão instalada do Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/check-version)
Para verificar a versão que está instalada da sua instância [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises) do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core),
acesse o seguinte endereço a partir de um navegador:
```text
https://endereco-do-seu-restpki-core/api/system/info
```
Exemplo: https://core.pki.rest/api/system/info
O retorno é um JSON contendo a versão do sistema, por exemplo:
```json
{"productName":"Lacuna Rest PKI Core","productVersion":"*.*.* RTM","spaVersion":"*.*.*","timestamp":"..."}
```
# [Configuração de Blob Storage](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-blob-storage)
A seção de configuração **BlobStorage** define como a aplicação armazena e recupera arquivos.
## Sistema de arquivos
Armazena os arquivos no sistema de arquivos. Configurações disponíveis:
* **Path**: o caminho onde os arquivos serão armazenados.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=FileSystem
BlobStorage__Path=/var/files/myapp
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=FileSystem
Path=/var/files/myapp
```
Exemplo (configuração JSON):
```json
{
...,
"BlobStorage": {
"Type": "FileSystem"
"Path": "/var/files/myapp"
},
...
}
```
## Azure Storage Account
Armazena os arquivos em um container de Azure Storage Account. Configurações disponíveis:
* **ConnectionString**: a connection string da conta de armazenamento.
* **ContainerName**: o nome do container onde os arquivos serão armazenados.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=Azure
BlobStorage__ConnectionString=DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net
BlobStorage__ContainerName=myapp-container
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=Azure
ConnectionString="DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net"
ContainerName=myapp-container
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "Azure",
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net",
"ContainerName": "myapp-container"
},
...
}
```
## AWS S3 (Simple Storage Service)
Armazena os arquivos em um bucket do S3. Configurações disponíveis:
* **Region**: a região do bucket.
* **BucketName**: o nome do bucket onde os arquivos serão armazenados.
* **AccessKey**: o ID da chave de acesso de um usuário IAM com acesso ao bucket.
* **SecretKey**: a chave de acesso secreta de um usuário IAM com acesso ao bucket.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=AwsS3
BlobStorage__Region=us-east-1
BlobStorage__BucketName=myappbucket
BlobStorage__AccessKey=MYACCESSKEYID
BlobStorage__SecretKey=MYSECRETACCESSKEY
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=AwsS3
Region=us-east-1
BucketName=myappbucket
AccessKey=MYACCESSKEYID
SecretKey=MYSECRETACCESSKEY
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "AwsS3",
"Region": "us-east-1",
"BucketName": "myappbucket",
"AccessKey": "MYACCESSKEYID",
"SecretKey": "MYSECRETACCESSKEY"
},
...
}
```
### Armazenamento de objetos compatível com S3
Também é possível usar um serviço de armazenamento de objetos compatível com S3 auto-hospedado, também conhecido como "clone do S3", por exemplo o [minIO](https://min.io/). Nesse caso, em vez da
*Region*, especifique o **Endpoint**.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=AwsS3
BlobStorage__Endpoint=http://localhost:9000
BlobStorage__BucketName=myappbucket
BlobStorage__AccessKey=MYACCESSKEYID
BlobStorage__SecretKey=MYSECRETACCESSKEY
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=AwsS3
Endpoint=http://localhost:9000
BucketName=myappbucket
AccessKey=MYACCESSKEYID
SecretKey=MYSECRETACCESSKEY
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "AwsS3",
"Endpoint": "http://localhost:9000",
"BucketName": "myappbucket",
"AccessKey": "MYACCESSKEYID",
"SecretKey": "MYSECRETACCESSKEY"
},
...
}
```
Por padrão, quando um armazenamento compatível com S3 é configurado, usa-se o endereçamento no estilo de caminho (path-style), em vez do endereçamento no estilo de host virtual
(virtual-hosted–style), que é o padrão quando um bucket do AWS S3 comum é configurado. Isso pode ser alterado com a configuração **ForcePathStyle** (`true` ou `false`). Para mais
informações sobre o endereçamento do S3, consulte [Virtual hosting of buckets](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html).
## Google Cloud Storage
Armazena os arquivos em um bucket do Google Cloud Storage. Você precisará de uma conta de serviço com o papel Storage Object Admin. Configurações disponíveis:
* **BucketName**: o nome do bucket onde os arquivos serão armazenados.
* **ProjectId**: o ID do projeto do Google Cloud.
* **PrivateKeyId**: o ID da chave privada da conta de serviço.
* **PrivateKey**: a chave privada da conta de serviço.
* **ClientEmail**: o e-mail da conta de serviço.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=Gcp
BlobStorage__BucketName=myappbucket
BlobStorage__ProjecId=my-app-project-id
BlobStorage__PrivateKeyId=MYPRIVATEKEYID
BlobStorage__PrivateKey=MYPRIVATEKEY
BlobStorage__ClientEmail=myserviceaccountemail@my-app-project-id.iam.gserviceaccount.com
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=Gcp
BucketName=myappbucket
ProjecId=my-app-project-id
PrivateKeyId=MYPRIVATEKEYID
PrivateKey=MYPRIVATEKEY
ClientEmail=myserviceaccountemail@my-app-project-id.iam.gserviceaccount.com
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "Gcp",
"BucketName": "myappbucket",
"ProjecId": "my-app-project-id",
"PrivateKeyId": "MYPRIVATEKEYID",
"PrivateKey": "MYPRIVATEKEY",
"ClientEmail": "myserviceaccountemail@my-app-project-id.iam.gserviceaccount.com"
},
...
}
```
### Obtendo as configurações do Google Cloud Storage
Primeiro, é preciso um bucket do Google Cloud Storage para armazenar os arquivos. Para criar um novo bucket, use o botão **Create** do console do Google Cloud Storage:

Escolha um nome para o novo bucket e clique em continuar:

Escolha a região do novo bucket:

Certifique-se de aplicar a prevenção de acesso público e o controle de acesso uniforme ao novo bucket:

Escolha a política de retenção de objetos e crie o bucket:

Pode aparecer um diálogo para confirmar que o acesso público será impedido. Clique em confirmar para continuar:

Em seguida, é preciso criar uma conta de serviço do Google Cloud para acessar o bucket. Para isso, use o botão **Create service account** da página *Service Accounts*, em *IAM & Admin*:

Também é preciso um nome para a nova conta de serviço. Escolha-o e clique em **Create and continue**:

Certifique-se de que a nova conta de serviço receba o papel Storage Object Admin:

Conceda acesso a entidades de segurança (principals), se necessário, e clique em concluir para terminar a criação da nova conta de serviço:

Com a conta de serviço criada, é preciso uma chave para autenticá-la. Clique na nova conta de serviço para abrir a página de detalhes:

Nos detalhes da conta de serviço, vá para a seção de chaves:

Na opção **Add key**, escolha **Create new key**:

Escolha o formato JSON e clique no botão **Create**:

Será baixado um arquivo JSON com a chave privada e outras informações da conta de serviço:

No arquivo JSON, você encontra os campos `project_id`, `private_key_id`, `private_key` e `client_email` para configurar o serviço de Blob Storage.
> **Info**
>
> As informações deste arquivo JSON dão acesso aos recursos do GCP e não podem ser comprometidas! Armazene ou descarte o arquivo de forma segura.
# [Configuração do envio de e-mails - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-email)
Para habilitar o envio de e-mails, preencha as seguintes configurações da seção **Email**:
* **Enabled**: defina como `True`
* **ServerHost**: nome do host do servidor SMTP
* **Username** e **Password**: se o servidor SMTP exigir autenticação, defina estas configurações
* **SenderAddress**: endereço de e-mail a ser usado como remetente (campo *from*)
* **SenderName**: nome a ser usado como nome do remetente (opcional)
* **ServerPort**: por padrão, a comunicação SMTP é feita pela porta 587. Defina esta configuração para usar uma porta diferente
* **EnableSsl**: por padrão, a comunicação SMTP é feita sobre SSL. Para desabilitar o SSL, defina esta configuração como `false`
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[Email]
Enabled=True
ServerHost=smtp.yourcompany.com
Username=YOUR_USERNAME
Password=YOUR_PASSWORD
SenderAddress=yourapp@yourcompany.com
SenderName=Your App
```
Exemplo (variáveis de ambiente):
```sh
Email__Enabled=True
Email__ServerHost=smtp.yourcompany.com
Email__Username=YOUR_USERNAME
Email__Password=YOUR_PASSWORD
Email__SenderAddress=yourapp@yourcompany.com
Email__SenderName=Your App
```
Exemplo (arquivo de configuração *.json*):
```json
"Email": {
"Enabled": true,
"ServerHost": "smtp.yourcompany.com",
"Username": "YOUR_USERNAME",
"Password": "YOUR_PASSWORD",
"SenderAddress": "yourapp@yourcompany.com",
"SenderName": "Your App"
},
```
## Veja também
* [Rest PKI Core on-premises](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises)
# [Configuração de OpenID Connect - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-oidc)
Para fazer o gerenciamento de usuários, o [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) requer um servidor OpenID Connect (OIDC), mais especificamente uma assinatura do [GrantID](https://docs.lacunasoftware.com/articles/grant-id).
Você pode usar uma assinatura SaaS em [grantid.com](https://grantid.com/) ou [executar sua própria instância do GrantID](https://docs.lacunasoftware.com/articles/grant-id/on-premises).
## Configurando o GrantID
Entre no portal do Console do GrantID e siga as instruções abaixo.
### Crie uma conexão de API
Na aba **Scopes**, clique em **New API Connection** para criar uma conexão de API para a aplicação, informando:
* **Name**: `restpkicore`
* **Display name**: `Rest PKI Core`
* **User Informations (claims)**: marque todos
### Aplicação frontend
Na aba **Applications**, clique em **New Application** e informe:
* **Name**: `Rest PKI Core`
* **App-id**: `restpkicore`
* **Type**: selecione `Implicit flow`.
Clique na aba **Settings** e defina:
* Marque a caixa de seleção `Allow Access tokens via browser?` (se ainda não estiver marcada)
* Desmarque as seguintes caixas de seleção (se estiverem marcadas): `Require consent?`, `Front-channel logout session required?` e `Back-channel logout session required?`
* **Application URL**: a URL da sua aplicação. Exemplo: `https://restpkicore.yourcompany.com`
* **Allowed URLs**:
* **Redirect**: você deve adicionar 4 URLs de redirecionamento baseadas na URL da sua aplicação:
* `` sem barra no final.
* `/silent-refresh.html`
* `/private/profile/refresh`
* `/private`
* **CORS origins**: `` sem barra no final.
* **Post Logout**: `` sem barra no final.
* **Allowed Identification Scopes**: marque todos
* **Allowed API scopes**: marque a caixa de seleção do escopo de API `Rest PKI Core`
* Marque as seguintes caixas de seleção: `Enable digital certificate login?`, `Prioritize digital certificate login?` e `Require digital certificate login?`
> **Aviso**
>
> Não se esqueça de salvar as alterações clicando no botão ao final da página.
### Aplicação backend
Na aba **Applications**, clique novamente em **New Application** e informe:
* **Name**: `Rest PKI Core Backend`
* **App-id**: `restpkicore-backend`
* **Type**: selecione `Client credentials`.
Clique na aba **Settings** e defina:
* **Allowed API scopes**: marque a caixa de seleção `Manage subscription's users`.
> **Aviso**
>
> Não se esqueça de salvar as alterações clicando no botão ao final da página.
Depois de salvar, volte à aba **Basic Details** e, na seção **App Secret**, clique em **Generate** e depois em **Generate Secret**.
Esse é o valor da configuração `AppSecret` (copie-o e guarde-o para usar depois).
## Configurando o Rest PKI Core para usar o GrantID
Depois de seguir os passos acima para configurar o GrantID, preencha a seção **Oidc** do arquivo de configuração:
* **Authority**: a autoridade OIDC (ex.: *https://login.id.patorum.com*)
* **ApiEndpoint**: o endpoint de API do servidor OIDC (ex.: *https://api.id.patorum.com*)
* **ApiName**: `restpkicore`
* **ClientAppId**: `restpkicore`
* **AppId**: `restpkicore-backend`
* **AppSecret**: o secret gerado para a aplicação backend
* **RequireHttps** (opcional): defina como `false` se a instância do GrantID não usar HTTPS
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[Oidc]
Enabled=True
Authority=https://login.id.patorum.com
ApiEndpoint=https://api.id.patorum.com
ApiName=restpkicore
ClientAppId=restpkicore
AppId=restpkicore-backend
AppSecret=YOUR_BACKEND_APP_SECRET
```
Exemplo (variáveis de ambiente):
```bash
Oidc__Enabled=True
Oidc__Authority=https://login.id.patorum.com
Oidc__ApiEndpoint=https://api.id.patorum.com
Oidc__ApiName=restpkicore
Oidc__ClientAppId=restpkicore
Oidc__AppId=restpkicore-backend
Oidc__AppSecret=YOUR_BACKEND_APP_SECRET
```
## Veja também
* [GrantID](https://docs.lacunasoftware.com/articles/grant-id)
# [Configuração de carimbo de tempo](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-timestamp)
> **Observação**
>
> Se você usa uma versão 1.x do Rest PKI Core, veja a [seção abaixo](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-timestamp#configuração-nas-versões-1x)
## Migrando da 1.x para a 2.x
Para migrar sem perder o histórico de requisições, é preciso usar o campo **Aliases**.
Configuração 1.x
```ini
[Timestamp]
MaxAutoRetryCount=2
RetryDelayInMilliseconds=500
[Timestamp:Timestampers:0]
Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
AuthenticationType=OAuthBearerToken
BearerToken=YOURTOKEN
Tiers=PkiBrazil
[Timestamp:Timestampers:1]
Url=https://tsp.patorum.com/timestamp
AuthenticationType=BasicAuthentication
Username=SOMEUSER
Password=SOMEPASS
```
Configuração 2.x
```ini
[Timestamp]
MaxAutoRetryCount=2
RetryDelayInMilliseconds=500
[Timestamp:Providers:Lacuna]
Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
AuthenticationType=OAuthBearerToken
BearerToken=YOURTOKEN
Tiers=PkiBrazil
Aliases=Provider0
[Timestamp:Providers:Alternative]
Url=https://tsp.patorum.com/timestamp
AuthenticationType=BasicAuthentication
Username=SOMEUSER
Password=SOMEPASS
Aliases=Provider1
```
Na configuração 2.x, o campo **Aliases** deve ser preenchido com o nome do provedor na configuração 1.x, que se baseia na posição do provedor na lista de carimbadoras (timestampers).
## Configuração
A aplicação obtém carimbos de tempo X.509 quando necessário, por meio do Time-Stamp Protocol (RFC 3161), junto a provedores de carimbo de tempo compatíveis.
A seção de configuração **Timestamp** contém a configuração para a obtenção de carimbos de tempo. Configurações disponíveis:
* **Providers**: dicionário de provedores (veja abaixo)
* **Tiers**: dicionário de planos (veja abaixo)
* **RetryDelayMilliseconds**: se todos os provedores falharem, tempo de espera em milissegundos antes de tentar novamente (padrão: `1500`)
* **MaxAutoRetryCount**: número de vezes que a lista de provedores é percorrida até se obter um carimbo de tempo (padrão: `3`)
* **LacunaTestTimestamperEnabled**: se a carimbadora de testes da Lacuna será usada com o plano `LacunaTest` (padrão: `True`)
O dicionário **Providers** contém a configuração de cada provedor de carimbo de tempo. As configurações disponíveis para cada item são:
* **Url**: a URL completa para a qual as requisições de carimbo de tempo são enviadas (ex.: `https://pki.rest/tsp`)
* **Aliases**: lista, separada por vírgulas, de identificadores anteriores do provedor (útil para renomear provedores sem quebrar o histórico de requisições)
* **DisplayName**: nome de exibição do provedor
* **AuthenticationMethod**: o modo de autenticação exigido pelo provedor de carimbo de tempo (veja os métodos suportados abaixo)
* **DefaultPolicy**: OID da política padrão a ser usada pela TSA ao solicitar um carimbo de tempo. Só é aplicada se a requisição não informar um `tsaPolicyId`
* **Priority**: define a prioridade de uso. Quanto mais próximo de zero, maior a prioridade (padrão: `1.000.000`)
* **DangerousAcceptAnyServerCertificate**: se a verificação do certificado do provedor deve ser ignorada. Use com cautela, pois isso pode expor a aplicação a riscos de segurança (padrão: `false`)
* **ServerCertificateThumbprint**: thumbprint SHA do certificado do servidor. Serve para garantir que a conexão seja feita com o servidor correto, verificando o certificado do servidor (suporta SHA-1, SHA-256, SHA-384 e SHA-512)
* **StoreRawResponse**: armazena a resposta bruta do carimbo de tempo, o arquivo .tsr (padrão: `false`)
* **Tiers**: lista, separada por vírgulas, dos planos de carimbo de tempo (tiers) para os quais esta carimbadora pode ser usada. Se omitida, a carimbadora será usada para todos os planos. Pode conter os IDs ou os nomes dos planos (ex.: `PkiBrazil,LacunaTest`)
Dependendo do modo de autenticação exigido pelo provedor de carimbo de tempo, são necessárias configurações adicionais:
* `None`: nenhuma configuração adicional é necessária
* `BasicAuthentication`: preencha **Username** e **Password**
* `OAuthBearerToken`: preencha **BearerToken**
* `MutualSSL`
* Se o certificado cliente estiver armazenado em um arquivo PFX (PKCS#12) contendo o certificado cliente, preencha:
* **PfxPath** com o caminho do arquivo ou **PfxContent** com o conteúdo do arquivo em Base64
* **PfxPassword** com a senha do arquivo PFX
* Se o certificado cliente estiver no repositório de certificados do sistema operacional, preencha:
* **CertificateThumbprint** com o thumbprint SHA-1 do certificado em hexadecimal (em minúsculas ou maiúsculas)
* **CertificateStore** (opcional): defina como `LocalMachine` para usar o repositório do computador (padrão: `CurrentUser`)
* `ApiKey`: preencha **ApiKey**
* `CustomAuthorizationHeader`: preencha **AuthorizationHeader** com o valor do header de autorização, normalmente na forma de um esquema
seguido de um valor (ex.: `ApiKey SOME_VALUE`)
### Seleção e prioridade de provedores
Quando um carimbo de tempo é solicitado para um plano específico, todos os provedores desse plano são selecionados e ordenados por prioridade. A prioridade é determinada pelo valor do campo **Priority**. Quanto menor o valor, maior a prioridade (o padrão é `1.000.000`).
Se vários provedores tiverem a mesma prioridade, o provedor é escolhido por um algoritmo de alternância (round-robin). Isso garante que a carga seja distribuída igualmente entre os provedores de mesma prioridade.
### Exemplos
Exemplo típico (arquivo de configuração *.ini* ou *.conf*):
```ini
[Timestamp:Providers:Lacuna]
Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
AuthenticationMethod=OAuthBearerToken
BearerToken=YOURTOKEN
```
Exemplo típico (variáveis de ambiente):
```bash
Timestamp__Providers__Lacuna__Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
Timestamp__Providers__Lacuna__AuthenticationMethod=OAuthBearerToken
Timestamp__Providers__Lacuna__BearerToken=YOURTOKEN
```
Exemplo com 2 provedores e configurações avançadas (arquivo de configuração *.ini* ou *.conf*):
```ini
[Timestamp]
MaxAutoRetryCount=2
RetryDelayMilliseconds=500
[Timestamp:Providers:Lacuna]
Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
AuthenticationMethod=OAuthBearerToken
BearerToken=YOURTOKEN
Priority=0
[Timestamp:Providers:Alternative]
Url=https://tsp.patorum.com/timestamp
AuthenticationMethod=BasicAuthentication
Username=SOMEUSER
Password=SOMEPASS
```
Exemplo com 2 provedores e configurações avançadas (variáveis de ambiente):
```bash
Timestamp__MaxAutoRetryCount=2
Timestamp__RetryDelayMilliseconds=500
Timestamp__Providers__Lacuna__Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
Timestamp__Providers__Lacuna__AuthenticationMethod=OAuthBearerToken
Timestamp__Providers__Lacuna__BearerToken=YOURTOKEN
Timestamp__Providers__Lacuna__Priority=0
Timestamp__Providers__Alternative__Url=https://tsp.patorum.com/timestamp
Timestamp__Providers__Alternative__AuthenticationMethod=BasicAuthentication
Timestamp__Providers__Alternative__Username=SOMEUSER
Timestamp__Providers__Alternative__Password=SOMEPASS
```
## Planos de carimbo de tempo
É possível restringir o uso de cada provedor a uma lista de planos de carimbo de tempo (tiers). Para isso, preencha a configuração `Tiers` de cada provedor, por exemplo:
```bash
[Timestamp:Providers:Lacuna]
Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
AuthenticationMethod=OAuthBearerToken
BearerToken=YOURTOKEN
Tiers=PkiBrazil
```
Além disso, na seção **Timestamp** você pode preencher o dicionário **Tiers** para definir nomes de exibição para os planos de carimbo de tempo, assim como aliases (útil para renomear
planos sem quebrar o histórico de requisições). As configurações disponíveis para cada item são:
* **DisplayName**: nome de exibição do plano
* **Aliases**: lista, separada por vírgulas, de identificadores anteriores do plano
Exemplo:
```ini
[Timestamp:Tiers:PkiBrazil]
DisplayName=ICP-Brasil (production only!)
[Timestamp:Tiers:Test]
DisplayName=Tests (development only!)
Aliases=TestTier,Tests
[Timestamp:Providers:Lacuna]
Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
AuthenticationMethod=OAuthBearerToken
BearerToken=YOURTOKEN
Tiers=PkiBrazil
[Timestamp:Providers:Free]
Url=https://somefreeprovider.com/tsp
Tiers=Test
```
## Configuração nas versões 1.x
Nas versões 1.x, só estava disponível uma sintaxe, hoje obsoleta, para configurar o carimbo de tempo.
> **Observação**
>
> A sintaxe antiga continua sendo suportada; portanto, se você está atualizando a partir da 1.x, não é obrigatório alterar a sua configuração
A seção de configuração **Timestamp** contém a configuração para a obtenção de carimbos de tempo. Configurações disponíveis:
* **Timestampers**: lista de provedores de carimbo de tempo (veja abaixo)
* **RetryDelayInMilliseconds**: se todos os provedores falharem, tempo de espera em milissegundos antes de tentar novamente (padrão: `1500`)
* **MaxAutoRetryCount**: número de vezes que a lista de provedores é percorrida até se obter um carimbo de tempo (padrão: `3`)
* **LacunaTestTimestamperEnabled**: se a carimbadora de testes da Lacuna será usada com o plano `LacunaTest` (padrão: `True`)
A lista **Timestampers** contém a configuração de cada provedor de carimbo de tempo. As configurações disponíveis são:
* **Url**: a URL completa para a qual as requisições de carimbo de tempo são enviadas (ex.: `https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310`)
* **AuthenticationType**: o modo de autenticação exigido pelo provedor de carimbo de tempo. Os modos suportados são `None`, `BasicAuthentication` e `OAuthBearerToken` (veja abaixo).
* **Tiers**: lista, separada por vírgulas, dos planos de carimbo de tempo (tiers) para os quais esta carimbadora pode ser usada. Se omitida, a carimbadora será usada para todos os planos. Pode conter os IDs ou os nomes dos planos (ex.: `PkiBrazil,LacunaTest`)
Dependendo do modo de autenticação exigido pelo provedor de carimbo de tempo, são necessárias configurações adicionais:
* `None`: nenhuma configuração adicional é necessária
* `BasicAuthentication`: preencha **Username** e **Password**
* `OAuthBearerToken`: preencha **BearerToken**
Exemplo típico (arquivo de configuração *.ini* ou *.conf*):
```ini
[Timestamp:Timestampers:0]
Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
AuthenticationType=OAuthBearerToken
BearerToken=YOURTOKEN
Tiers=PkiBrazil
```
Exemplo típico (variáveis de ambiente):
```bash
Timestamp__Timestampers__0__Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
Timestamp__Timestampers__0__AuthenticationType=OAuthBearerToken
Timestamp__Timestampers__0__BearerToken=YOURTOKEN
Timestamp__Timestampers__0__Tiers=PkiBrazil
```
Exemplo mais avançado (arquivo de configuração *.ini* ou *.conf*):
```ini
[Timestamp]
MaxAutoRetryCount=2
RetryDelayInMilliseconds=500
[Timestamp:Timestampers:0]
Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
AuthenticationType=OAuthBearerToken
BearerToken=YOURTOKEN
Tiers=PkiBrazil
[Timestamp:Timestampers:1]
Url=https://tsp.patorum.com/timestamp
AuthenticationType=BasicAuthentication
Username=SOMEUSER
Password=SOMEPASS
```
Exemplo mais avançado (variáveis de ambiente):
```bash
Timestamp__MaxAutoRetryCount=2
Timestamp__RetryDelayInMilliseconds=500
Timestamp__Timestampers__0__Url=https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310
Timestamp__Timestampers__0__AuthenticationType=OAuthBearerToken
Timestamp__Timestampers__0__BearerToken=YOURTOKEN
Timestamp__Timestampers__0__Tiers=PkiBrazil
Timestamp__Timestampers__1__Url=https://tsp.patorum.com/timestamp
Timestamp__Timestampers__1__AuthenticationType=BasicAuthentication
Timestamp__Timestampers__1__Username=SOMEUSER
Timestamp__Timestampers__1__Password=SOMEPASS
```
# [Criação de organização - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/create-sub)
O [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) suporta múltiplas "organizações", ou "contas", cada uma tendo suas próprias aplicações, chaves de API, contextos de segurança etc.
Para criar uma organização, siga os passos abaixo. Você precisa ter privilégios administraticos globais na instância para realizar esse procedimento
(papel `Proprietário` ou `Contribuidor` no Sys Admin).
1. Autentique-se no painel de controle da sua instância
1. No canto superior direito, clique no retângulo com seu nome e em seguida em **Sys Admin**
1. Na página de organizações, clique em **Adicionar**
1. Preencha o **nome** da nova organização e opcionalmente um apelido, em seguida clique em **Criar**
1. Aceite a recomendação para recarregar a página clicando em **OK**
> **Observação**
>
> Organizações são armazenadas em cache em cada servidor web, por isso essa mudança pode demorar até 1 minuto para ser propagada.
Em seguida, você provavelmente desejará **adicionar um usuário com privilégios administrativos** à organização:
1. Espere um minuto inteiro para a criação da organização ser propagada
1. Recarregue a página manualmente clicando no botão ↻ do seu navegador ou pressionando a tecla F5
1. Clique no retângulo com seu nome no canto superior direito, em seguida em **Sair do Sys Admin**
1. No menu esquerdo, clique em **Usuários**, em seguida em **Adicionar**
1. Pesquise por um usuário digitando seu nome ou endereço de email. Se o usuário ainda não tiver se registrado, você não o encontrará. Nesse caso, preencha
o endereço de email completo dele.
1. Em *Organização*, selecione a organização recém-criada
1. Escolha um ou mais papéis para o usuário. Para conceder privilégios administrativos máximos ao usuário sobre a organização, marque o papel `Administrador`
1. Clique em **Adicionar usuário**
> **Observação**
>
> Se a organização não aparecer na lista de organizações, provavelmente a propagação ainda está em curso. Aguarde mais alguns segundos e recarregue a página.
Se você selecionou um usuário existente no passo 5, o usuário já está com acesso e pode imediatamente acessar o painel de controle. Nenhuma notificação será enviada,
você mesmo precisa notificar o usuário.
Caso o usuário ainda não tenha se registrado, ele receberá um email de convite com instruções o guiando pelo processo de cadastro e, ao final do processo, o acesso
à organização será concedido.
Uma vez que o usuário consiga acessar o painel de controle da organização, ele mesmo pode criar uma chave de API para integração seguindo os passos no artigo
[Primeiros passos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started).
# [Setup do Rest PKI Core em Docker](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/docker)
Para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises) do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) em Docker, siga os passos abaixo. Para outros ambientes, [clique aqui](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises#platforms).
A imagem de Docker do Rest PKI Core encontra-se disponível no Docker Hub:
**[Rest PKI Core Docker image](https://hub.docker.com/r/lacunasoftware/restpkicore)**
A imagem atualmente recomendada é a `lacunasoftware/restpkicore:5.0`
Tags móveis disponíveis:
* A tag `5.0` aponta para a última imagem 5.0.x **(recomendada)**
* A tag `5` aponta para a última imagem 5.x
* A tag `stable` aponta para a última imagem estável
Esta imagem requer:
* Um **blob storage** compartilhado entre todos os containers rodando a imagem -- veja [Configuração de Blob Storage](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-blob-storage)
* **Connection string** para um [banco de dados SQL Server ou PostgreSQL criado previamente](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/prepare-database)
* Licença de uso da biblioteca **PKI SDK** (em formato binário/Base64)
* Licença de uso do componente **Web PKI** (em formato binário/Base64)
## Configuração
Esta imagem é configurada utilizando variáveis de ambiente. Obtenha o [**arquivo de configuração de exemplo**](https://cdn.lacunasoftware.com/restpkicore/docker/restpkicore.env)
para um guia de como preencher os parâmetros necessários.
Para preencher o parâmetro `General__EncryptionKey`, gere uma chave de 256 bits para encriptar dados sensíveis armazenados no banco de dados:
```sh
docker run lacunasoftware/restpkicore:5.0 -- gen-enc-key
```
Para preencher o parâmetro `General__RootPasswordHash`, escolha uma senha forte para acesso à interface de gerenciamento como *root* e calcule o hash dela:
```sh
docker run -i lacunasoftware/restpkicore:5.0 -- hash-root-pass
```
## Portas
A imagem escuta na **porta 80**.
É recomendado configurar um proxy reverso ou balanceador de carga escutando nas portas padrão HTTP (80) e HTTPS (443), redirecionando o tráfego em
ambas portas para a porta 80 do Rest PKI Core. Ademais, seu proxy deve preencher os *request headers*
[X-Forwarded-Proto](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto),
[X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) e, opcionalmente, `X-Forwarded-Port`.
A configuração `Bindings__UseReverseProxy=True` instrui o Rest PKI Core a confiar nas informações preenchidas nestes headers.
## Exemplo
Em um ambiente de produção, tipicamente se utilizaria um orquestrador de Docker e um SGBD dedicado ou em nuvem (IaaS).
Entretanto, para fins de teste, utilizando apenas o Docker é possível rodar uma instância do Rest PKI Core com um banco de dados PostgreSQL local.
Comece criando um volume para o banco de dados:
```sh
docker volume create restpkicore_sql
```
Inicie o SGBD com uma senha da sua preferência (substitua `SOME_PASS` abaixo):
```sh
docker run --name restpkicore_sql -v restpkicore_sql:/var/lib/postgresql/data -p 5432:5432 -e "POSTGRES_PASSWORD=SOME_PASS" -d postgres
```
Verifique os logs do container para eventuais erros:
```sh
docker logs -f restpkicore_sql
```
Esse processo pode levar alguns minutos. Uma vez que o SGBD esteja executando, pressione CTRL+C para sair dos logs.
Crie um volume para utilizar como *blob storage*:
```sh
docker volume create restpkicore_data
```
Em seguida, baixe o [arquivo de configuração de exemplo](https://cdn.lacunasoftware.com/restpkicore/docker/restpkicore.env), salve-o com nome
*restpkicore.env* e preencha-o.
Na configuração da *connection string*, use o valor abaixo substituindo `HOST_IP` pelo endereço de IP da máquina *host* e `SOME_PASS`
pela senha escolhida para o banco de dados:
```sh
ConnectionStrings__DefaultConnection=Host=HOST_IP;Database=restpkicore;Username=postgres;Password=SOME_PASS
ConnectionStrings__DefaultConnection_ProviderName=Postgres
```
Na configuração de *blob storage*, deixe os parâmetros padrões, pois montaremos o volume criado em `/var/app`:
```sh
BlobStorage__Type=FileSystem
BlobStorage__Path=/var/app
```
Por fim, execute um container com a imagem usando o arquivo de configuração, montando o volume `restpkicore_data` em `/var/app` e expondo a aplicação (que escuta
na porta 80) na porta 8080 da máquina *host*:
```sh
docker run --name restpkicore --env-file restpkicore.env -v restpkicore_data:/var/app -p 8080:80 -d lacunasoftware/restpkicore:5.0
```
> **Dica**
>
> Caso tenha privilégios suficientes, o Rest PKI Core tentará criar o banco de dados no servidor caso ele não exista (é isso que acontecerá nesse caso)
Verifique os logs do container para eventuais erros de configuração:
```sh
docker logs -f restpkicore
```
Se tudo estiver configurado corretamente, você deve ter uma instância do Rest PKI Core rodando em [localhost:8080](http://localhost:8080/)
## Veja também
* [Configuração de gerenciamento de usuários](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-oidc)
# [Logs - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/logging)
O [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) pode gravar logs nos seguintes destinos:
* Saída padrão (*stdout*)
* Arquivo
* [Azure Table storage](https://azure.microsoft.com/en-us/products/storage/tables/)
O destino padrão depende da plataforma usada.
Plataforma | Destino padrão dos logs
---------- | -----------------------
Linux | Saída padrão, que é registrada automaticamente no log do sistema (já que a aplicação é executada como daemon)
Docker | Saída padrão, o que em geral significa que os logs acabam nos logs do orquestrador
Azure | Azure Table
## Níveis
Os logs são classificados em um de 6 níveis de severidade:
LogLevel | Descrição
----------- | ---------
Trace | Contém as mensagens mais detalhadas. Essas mensagens podem conter dados sensíveis da aplicação. Ficam desabilitadas por padrão e ***não*** devem ser habilitadas em produção.
Debug | Para depuração e desenvolvimento. Use com cautela em produção, por causa do grande volume.
Information | Acompanha o fluxo geral da aplicação. Pode ter valor a longo prazo.
Warning | Para eventos anormais ou inesperados. Em geral, inclui erros ou condições que não fazem a aplicação falhar.
Error | Para erros e exceções que não podem ser tratados. Essas mensagens indicam uma falha na operação ou requisição atual, não uma falha de toda a aplicação.
Critical | Para falhas que exigem atenção imediata. Exemplos: cenários de perda de dados, falta de espaço em disco.
Por padrão, são registrados os logs com severidade **Warning ou superior**.
Isso pode ser alterado, por exemplo, para habilitar os logs com severidade Information ou superior:
```ini
[Logging:LogLevel]
Default=Information
```
Ou, com variáveis de ambiente:
```sh
Logging__LogLevel__Default=Information
```
## Categorias
Os logs também são classificados em categorias, que podem ser usadas para habilitar logs com uma severidade mínima diferente conforme a categoria. Por exemplo, para habilitar logs
com severidade Debug ou superior apenas das categorias que começam com o prefixo `Lacuna.RestPki` (a relevância desse prefixo é explicada abaixo):
```ini
[Logging:LogLevel]
Lacuna.RestPki=Debug
```
Ou, com variáveis de ambiente:
```sh
Logging__LogLevel__Lacuna.RestPki=Debug
```
Abaixo estão alguns prefixos de categoria úteis.
Prefixo da categoria | Descrição
---------------------------------------- | ---------
Lacuna.RestPki | Logs gerados pela própria aplicação (e não por bibliotecas ou dependências externas)
Microsoft.EntityFrameworkCore | Todas as mensagens do Entity Framework Core (framework de acesso a dados usado pela aplicação)
Microsoft.EntityFrameworkCore.Database | Todas as interações com o banco de dados
Microsoft.AspNetCore.Hosting.Diagnostics | Requisições e respostas HTTP
## Habilitando o registro de dados sensíveis
Por padrão, dados sensíveis, como o conteúdo das requisições HTTP ou os parâmetros dos comandos SQL, não são registrados no log, independentemente do nível mínimo de severidade
configurado. Para habilitar o registro de dados sensíveis:
```ini
[General]
EnableSensitiveDataLogging=True
```
Ou, com variáveis de ambiente:
```sh
General__EnableSensitiveDataLogging=True
```
## Formatação JSON / logs estruturados
O Rest PKI Core gera logs estruturados. No entanto, por padrão, os logs são gravados na saída padrão em um formato legível por humanos, que "achata" as informações em uma
única string, perdendo a estrutura que, de outra forma, estaria disponível.
Para evitar essa perda de dados, habilite a formatação JSON nos logs gravados na saída padrão:
```ini
[Logging:Console]
FormatterName=json
```
Ou, com variáveis de ambiente:
```sh
Logging__Console__FormatterName=json
```
## Gravando logs em arquivo
Para gravar os logs em um arquivo, configure a aplicação da seguinte forma:
```ini
[Serilog:WriteTo:0]
Name=File
[Serilog:WriteTo:0:Args]
path=PATH_TO_LOG_FILE
outputTemplate={Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}
```
Ou, com variáveis de ambiente:
```sh
Serilog__WriteTo__0__Name=File
Serilog__WriteTo__0__Args__path=PATH_TO_LOG_FILE
Serilog__WriteTo__0__Args__outputTemplate={Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}
```
Para mais informações sobre a configuração `outputTemplate`, consulte [Formatting Output](https://github.com/serilog/serilog/wiki/Formatting-Output).
Observe que, ao gravar logs em arquivo, a configuração do nível mínimo de severidade é diferente:
```ini
[Serilog:MinimumLevel]
Default=Error
[Serilog:MinimumLevel:Override]
Lacuna.RestPki=Information
Microsoft.EntityFrameworkCore.Database=Debug
```
```sh
Serilog__MinimumLevel__Default=Error
Serilog__MinimumLevel__Override__Lacuna.RestPki=Information
Serilog__MinimumLevel__Override__Microsoft.EntityFrameworkCore.Database=Debug
```
Além disso, nessa sintaxe de configuração os níveis `Trace` e `Critical` se chamam, respectivamente, `Verbose` e `Fatal`.
## Registrando requisições e respostas HTTP no log
Para habilitar o registro de requisições e respostas HTTP no log:
```ini
[HttpLogging]
Enabled=True
```
Ou, com variáveis de ambiente:
```sh
HttpLogging__Enabled=True
```
> **_OBSERVAÇÃO:_** Para que o log funcione, é necessário configurar o nível de log da categoria `Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware` como `Information`
Feito isso, as requisições já serão registradas com uma configuração padrão. Se quiser mais controle sobre essas configurações, estão disponíveis as seguintes opções:
| Opção |Tipo | Descrição |
|----------------------------------|-----|-----------|
| Enabled |bool| Defina como `true` para habilitar o log HTTP; caso contrário, defina como `false`. |
| LoggingFields |string| Especifica os campos da requisição e da resposta a serem registrados. Por padrão, registra as propriedades e os headers da requisição e da resposta. Os valores suportados estão listados [aqui](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.httplogging.httploggingfields?view=aspnetcore-6.0). Obs.: substitui o LoggingFields padrão. |
| AdditionalLoggingFields |string| Campos adicionais a serem incluídos no log. |
| RequestHeaders |string| Especifica os headers de requisição HTTP que podem ser registrados. Um header não permitido é registrado com o valor `[Redacted]`. Obs.: substitui os headers de requisição padrão. |
| AdditionalRequestHeaders |string| Headers de requisição HTTP adicionais a serem incluídos no log. |
| ResponseHeaders |string| Especifica os headers de resposta HTTP que podem ser registrados. Um header não permitido é registrado com o valor `[Redacted]`. Obs.: substitui os headers de resposta padrão. |
| AdditionalResponseHeaders |string| Headers de resposta HTTP adicionais a serem incluídos no log. |
| MediaTypeOptions |string| Configura as codificações de um tipo de mídia específico. Obs.: substitui as opções de tipo de mídia padrão. |
| AdditionalMediaTypeOptions |string| Opções de tipo de mídia adicionais. |
| RequestBodyLogLimit |int| Limita o tamanho máximo do corpo da requisição a ser registrado (em bytes). Padrão: `32 KB`. |
| ResponseBodyLogLimit |int| Limita o tamanho máximo do corpo da resposta a ser registrado (em bytes). Padrão: `32 KB`. |
### Exemplos
Por padrão, o corpo da requisição não é registrado. Para habilitá-lo, use um dos seguintes métodos:
```ini
[HttpLogging]
LoggingFields=RequestBody # Override default logging fields
AdditionalLoggingFields=RequestBody # RequestBody will be appended to default logging fields
```
Ou, com variáveis de ambiente:
```sh
HttpLogging__LoggingFields=RequestBody
HttpLogging__AdditionalLoggingFields=RequestBody
```
Headers não permitidos aparecem com o valor `[Redacted]`. Para exibir o valor dos headers de que você precisa, use:
```ini
[HttpLogging]
AdditionalRequestHeaders=cookie,Sec-Fetch-Dest
```
Ou, com variáveis de ambiente:
```sh
HttpLogging__AdditionalRequestHeaders=cookie,Sec-Fetch-Dest
```
Se quiser limitar o tamanho do corpo da requisição registrado no log:
```ini
[HttpLogging]
RequestBodyLogLimit=5120 # limit to 5KB (5 * 1024) = 5120
```
Ou, com variáveis de ambiente:
```sh
HttpLogging__RequestBodyLogLimit=5120
```
# [Preparando um banco de dados para instalação do Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/prepare-database)
Para instalar uma [instalação *on premises*](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises) do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core), você precisará de uma string de conexão para um dos seguintes motores de banco de dados:
* [SQL Server](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/prepare-database#sql-server)
* [PostgreSQL](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/prepare-database#postgres)
## SQL Server
Para usar SQL Server, você precisará de um banco de dados com:
* Collation: `Latin1_General_100_CI_AI` ou `Latin1_General_CI_AI`
* Credenciais correspondentes a um usuário com a função `db_owner`
> **Observação**
>
> Se você preferir executar o Rest PKI Core sem conceder `db_owner` ao usuário da aplicação, consulte [este artigo](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/unprivileged-db-user)
> **Aviso**
>
> A collation do banco de dados **deve** ser uma das collations especificadas acima. Criar o banco de dados com uma collation diferente provavelmente causará falha na instalação!
Se você precisar de ajuda para preparar o banco de dados, siga as etapas neste artigo.
> **Observação**
>
> Você não precisa seguir estas instruções específicas. Se desejar preparar o banco de dados de forma diferente, por exemplo, usando recursos
> avançados como log shipping ou mirroring, você pode fazer isso, desde que a collation e as associações de funções sejam observadas.
Para criar o banco de dados, use a seguinte consulta T-SQL (opcionalmente alterando o nome do banco de dados):
```sql
USE master;
CREATE DATABASE RestPkiCore COLLATE Latin1_General_100_CI_AI;
GO
```
Crie um usuário e conceda a ele a função `db_owner`:
```sql
USE master;
CREATE LOGIN RestPkiCoreAdmin WITH PASSWORD = 'XXXXX';
GO
USE RestPkiCore;
CREATE USER RestPkiCoreAdmin FOR LOGIN RestPkiCoreAdmin;
EXEC sp_addrolemember 'db_owner', 'RestPkiCoreAdmin';
GO
```
A string de conexão seria então:
```text
Data Source=.;Initial Catalog=RestPkiCore;User ID=RestPkiCoreAdmin;Password=XXXXX
```
> **Observação**
>
> Esta string de conexão supõe que o servidor de banco de dados está instalado no mesmo servidor da aplicação web. Se este não for o caso,
> o valor após `Data Source=` precisará ser alterado.
## PostgreSQL
A partir da [versão 1.12.0](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog#v1-12-0) do Rest PKI Core, o PostgreSQL também é suportado. Qualquer versão atualmente suportada do PostgreSQL
também é suportada ([que atualmente significa versão 11 ou superior](https://www.postgresql.org/support/versioning/)).
Comece criando o banco de dados e um usuário para a aplicação (opcionalmente alterando os nomes do banco de dados e do usuário):
```text
postgres=# CREATE DATABASE restpkicore;
CREATE DATABASE
postgres=# CREATE USER restpkicore WITH PASSWORD 'XXXXX';
CREATE ROLE
```
Em seguida, conecte-se ao banco de dados recém-criado e defina as permissões para o schema `public`:
```text
postgres=# \connect restpkicore
You are now connected to database "restpkicore" as user "postgres".
restpkicore=# REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE
restpkicore=# GRANT ALL ON SCHEMA public TO restpkicore;
GRANT
restpkicore=# CREATE SCHEMA hangfire;
CREATE
restpkicore=# GRANT ALL ON SCHEMA hangfire TO restpkicore;
GRANT
```
> **Observação**
>
> Por enquanto, apenas o uso de um usuário com acesso total ao schema é suportado. Entre em contato conosco se precisar executar o Rest PKI Core com menos privilégios.
A string de conexão seria então (supondo que você não alterou os nomes do banco de dados e do usuário):
```text
Host=localhost;Database=restpkicore;Username=restpkicore;Password=XXXXX
```
> **Observação**
>
> Esta string de conexão supõe que o servidor de banco de dados está instalado no mesmo servidor da aplicação web. Se este não for o caso,
> o valor após `Host=` precisará ser alterado.
## Veja também
* [Executando o RestPkiCore sem privilégios de db_owner](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/unprivileged-db-user) (apenas SQL Server)
# [Operando o Rest PKI Core sem ser *owner* do banco de dados](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/unprivileged-db-user)
O [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) pode operar de duas maneiras em relação ao acesso do banco de dados:
1. Sendo *owner* do banco de dados e atualizar automaticamente o modelo do banco de dados após uma atualização quando necessário (padrão)
1. Ter privilégios apenas de leitura e escrita no banco de dados, exigindo que o modelo do banco de dados seja atualizado pelo administrador usando uma ferramenta de linha de comando
Operar com o usuário da aplicação sendo *owner* do aplicativo é mais simples, por isso essa é a opção sugerida na instalação padrão. Entretanto, conceder somente privilégios de leitura e escrita
garante ao administrador maior controle sobre o banco de dados. Se você preferir operar o Rest PKI Core sem privilégios de *owner* para o usuário da aplicação, siga os passos descritos nesse artigo.
> **Observação**
>
> Por ora só é possível utilizar um usuário com acesso limitado no SQL Server. Contate-nos se precisar executar o Rest PKI Core com PostgreSQL usando um usuário com
> acesso limitado.
## Criando credenciais de leitura e escrita
Crie o banco de dados normalmente conforme descrito na [preparação do banco de dados](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/prepare-database).
Em seguida, ao invés de criar um usuário chamado *RestPkiCoreAdmin* e associá-lo ao papel `db_owner`, crie um usuário limitado chamado **RestPkiCoreApp**
e associe-o apenas aos papeis `db_datareader` e `db_datawriter`:
```sql
USE master;
CREATE LOGIN RestPkiCoreApp WITH PASSWORD = 'XXXXXX';
GO
USE RestPkiCore;
CREATE USER RestPkiCoreApp FOR LOGIN RestPkiCoreApp;
EXEC sp_addrolemember 'db_datareader', 'RestPkiCoreApp';
EXEC sp_addrolemember 'db_datawriter', 'RestPkiCoreApp';
GO
```
A *connection string* seria, então:
```text
Data Source=.;Initial Catalog=RestPkiCore;User ID=RestPkiCoreApp;Password=XXXXX
```
> **Observação**
>
> Essa *connection string* presume que o servidor do banco de dados esteja instalado no mesmo servidor que o aplicativo da web. Se este não for o caso,
> o valor após `Data Source=` deve ser alterado.
## Desabilitando a atualização automática do banco de dados
Operar com o Rest PKI Core sem privilégios de *owner* sobre o banco de dados significa que a aplicação não será capaz de atualizar o banco por conta própria.
Por isso, é preciso desabilitar a atualização automática do banco de dados de modo a evitar erros setando o parâmetro `General__AutoUpdateDatabase` para
`False` no arquivo de configuração.
## Veja também
* [Comando `update-db`](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/tool/update-db)
* [Preparando o banco de dados para instalação do Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/prepare-database)
# [Verificações de vulnerabilidades - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/vulnerabilities)
A imagem Docker do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) é verificada periodicamente em busca de vulnerabilidades conhecidas com o [Docker Scout](https://docs.docker.com/scout/). Esta página informa
as vulnerabilidades encontradas com severidade média ou superior e as ações realizadas para mitigá-las.
> **Observação**
>
> A detecção de uma vulnerabilidade não significa necessariamente que a aplicação esteja vulnerável. É preciso uma investigação mais aprofundada para determinar se uma dada vulnerabilidade
> representa de fato uma ameaça à aplicação.
Nossa política é manter a aplicação livre de vulnerabilidades de severidade crítica e alta e manter as de severidade média no mínimo. Se uma vulnerabilidade crítica ou alta
não puder ser corrigida, uma investigação mais aprofundada deve ser realizada e documentada aqui, para garantir que ela não represente uma ameaça real à aplicação.
## Verificação de vulnerabilidades 25.04
Período: de 2025-04-22 a 2025-04-23
Versão verificada: [2.5.0](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog#v2-5-0)
Vulnerabilidades encontradas:
* Severidade crítica: 0
* Severidade alta: **4**
* [CVE-2024-0056](https://scout.docker.com/vulnerabilities/id/CVE-2024-0056/org/lacunasoftware?s=github&n=System.Data.SqlClient&t=nuget&vr=%3C4.8.6&utm_source=hub&utm_medium=ExternalLink)
* [CVE-2025-27363](https://scout.docker.com/v/CVE-2025-27363?s=debian&n=freetype&ns=debian&t=deb&osn=debian&osv=11&vr=%3C2.10.4%2Bdfsg-1%2Bdeb11u2&utm_source=hub&utm_medium=ExternalLink)
* [CVE-2024-32655](https://scout.docker.com/v/CVE-2024-32655?s=github&n=Npgsql&t=nuget&vr=%3E%3D6.0.0%2C%3C6.0.11&utm_source=hub&utm_medium=ExternalLink)
* [CVE-2023-2603](https://scout.docker.com/v/CVE-2023-2603?s=debian&n=libcap2&ns=debian&t=deb&osn=debian&osv=11&vr=%3C1%3A2.44-1%2Bdeb11u1&utm_source=hub&utm_medium=ExternalLink)
* Severidade média: 6
* [CVE-2025-1390](https://scout.docker.com/v/CVE-2025-1390?s=debian&n=libcap2&ns=debian&t=deb&osn=debian&osv=11&vr=%3C1%3A2.44-1%2Bdeb11u1&utm_source=hub&utm_medium=ExternalLink)
* [CVE-2025-3576](https://scout.docker.com/v/CVE-2025-3576?s=debian&n=krb5&ns=debian&t=deb&osn=debian&osv=11&vr=%3E%3D1.18.3-6%2Bdeb11u5&utm_source=hub&utm_medium=ExternalLink)
* [CVE-2022-41064](https://scout.docker.com/v/CVE-2022-41064?s=github&n=System.Data.SqlClient&t=nuget&vr=%3C%3D4.8.4&utm_source=hub&utm_medium=ExternalLink)
* [CVE-2024-12243](https://scout.docker.com/v/CVE-2024-12243?s=debian&n=gnutls28&ns=debian&t=deb&osn=debian&osv=11&vr=%3C3.7.1-5%2Bdeb11u7&utm_source=hub&utm_medium=ExternalLink)
* [CVE-2024-12133](https://scout.docker.com/v/CVE-2024-12133?s=debian&n=libtasn1-6&ns=debian&t=deb&osn=debian&osv=11&vr=%3C4.16.0-2%2Bdeb11u2&utm_source=hub&utm_medium=ExternalLink)
* [CVE-2023-4641](https://scout.docker.com/v/CVE-2023-4641?s=debian&n=shadow&ns=debian&t=deb&osn=debian&osv=11&vr=%3C1%3A4.8.1-1%2Bdeb11u1&utm_source=hub&utm_medium=ExternalLink)
Medidas tomadas para mitigar:
* Atualização para o ASP.NET Core 8.0
* Atualização de várias dependências
* Remoção da dependência do pacote do sistema operacional `libgdiplus`
Vulnerabilidades após a mitigação:
* Severidade crítica: 0
* Severidade alta: 0
* Severidade média: 1
* [CVE-2025-3576](https://scout.docker.com/v/CVE-2025-3576?s=debian&n=krb5&ns=debian&t=deb&osn=debian&osv=12&vr=%3E%3D1.20.1-2%2Bdeb12u2&utm_source=hub&utm_medium=ExternalLink) (descoberta recentemente, sem correção disponível na época)
Versão com a mitigação: [3.0.0](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog#v3-0-0)
## Veja também
* [Verificando a versão do Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/check-version)
* [Histórico de versões](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog)
# [Rest PKI Core - Configuração no Windows Server](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/windows-server)
Para instalar uma instância *on premises* do Rest PKI Core no Windows Server, primeiro baixe os binários da versão desejada. A versão mais recente disponível é fornecida no link abaixo:
**[Pacote de binários do Rest PKI Core](https://cdn.lacunasoftware.com/restpkicore/restpkicore-5.0.0.zip)**
## Pré-requisitos
* Windows Server 2016 ou mais recente (qualquer edição)
* SQL Server 2014 ou mais recente (edição Standard ou superior recomendada)
* Licença do PKI SDK (em formato Base64)
* Licença do Web PKI (formato Base64/binário)
## Requisitos do Banco de Dados
* Banco de dados com collation `Latin1_General_100_CI_AI` ou `Latin1_General_CI_AI`.
* Credenciais correspondentes a um usuário com função `db_owner`.
Se precisar de ajuda para preparar o banco de dados, [clique aqui](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/prepare-database).
## Instalação
1. Instale o IIS
1. Instale o [ASP.NET Core Runtime 10.0 Hosting Bundle](https://dotnet.microsoft.com/download/dotnet/10.0) (seção *ASP.NET Core Runtime*, item *Hosting Bundle* em *Windows*)
1. Crie uma pasta para o site do IIS. Exemplo: `C:\inetpub\Rest PKI Core`
1. Crie uma pasta para logs. Exemplo: `C:\Logs`
1. Crie o site no IIS
1. No pool de aplicativos correspondente:
* Selecione Configurações Básicas e defina a versão do .NET CLR para `No managed code`

* Selecione Configurações Avançadas e defina load user profile para `True`

1. Extraia os arquivos do pacote de binários para a pasta do site.
> **Aviso**
>
> O Runtime de Hospedagem .NET Core deve ser instalado após o IIS, caso contrário, ao executar o site, você poderá receber o erro `500.19 (0x8007000d)`.
> Se este for o seu caso, execute a opção "Repair" do instalador do Runtime de Hospedagem.
## Configuração
1. Crie a pasta `C:\ProgramData\Lacuna Software\Rest PKI Core`
1. Na pasta do site, navegue para `config-templates\linux` e copie o arquivo `appsettings.conf` para a pasta recém-criada, renomeando-o para
`appsettings.ini` 
1. Abra o arquivo `C:\ProgramData\Lacuna Software\Rest PKI Core\appsettings.ini` e preencha as configurações conforme os comentários do
próprio arquivo, como a connection string do banco de dados (`[ConnectionStrings]`), a URL do site (`[General]`) e as licenças (`[PkiSuite]`)
1. Adicione a seção do blob storage ao `appsettings.ini` (veja outras opções em [Configuração de Blob Storage](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/configure-blob-storage)):
```ini
[BlobStorage]
Type=FileSystem
Path=C:\Path\Do\Storage
```
1. Salve e feche o arquivo
## Geração de encryption key e password hash
Para preencher a configuração `EncryptionKey` da seção `[General]`, gere uma chave de 256 bits para encriptar dados sensíveis armazenados no
banco de dados. Execute o seguinte comando na pasta do site (`C:\inetpub\Rest PKI Core`):
```sh
dotnet Lacuna.RestPki.Site.dll -- gen-enc-key
```
Para preencher a configuração `RootPasswordHash` da seção `[General]`, escolha uma senha forte para acesso à interface de gerenciamento como *root* e calcule o hash dela:
```sh
dotnet Lacuna.RestPki.Site.dll -- hash-root-pass
```
No IIS, inicie o servidor através do botão Start na barra lateral direita.
# [Guia de Operação - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/operation)
Os artigos abaixo descrevem as operações mais comuns em uma instância do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core):
* [Monitoramento do provimento de carimbos de tempo](https://docs.lacunasoftware.com/articles/rest-pki/core/operation/monitor-timestamping)
* [Solicitando carimbos de tempo ao Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/requesting-timestamps)
> **Observação**
>
> Se você precisar de instruções sobre uma operação diferente, contate-nos
## Veja também
* [Guia de Instalação](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises)
* [Guia de Integração](https://docs.lacunasoftware.com/articles/rest-pki/core/integration)
# [Monitoramento do provimento de carimbos de tempo - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/operation/monitor-timestamping)
Para monitorar o provimento de carimbos de tempo na sua instância do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core), siga os passos abaixo:
1. Autentique-se na interface de gerenciamento da sua instância (ex: *https://restpkicore.suaempresa.com*)
1. Clique no seu nome no canto superior direito, em seguida em **Sys Admin**
1. No menu lateral, clique em **Timestamping**
1. Clique em um dos planos (*Tiers*) de carimbo de tempo
1. Na tela de detalhes do plano de carimbo de tempo, a seção **Solicitações** mostra as solicitações recebidas para o plano em questão, permitindo
filtragem por data de início e/ou data de fim
1. Para ver mais informações sobre uma solicitação específica, clique no link *Mostrar detalhes* da solicitação em questão
1. Na dialog que se abrirá, é possível obter:
* A solicitação (*Timestamp Query*) em formato binário conforme RFC 3161
* A resposta (*Timestamp Reply*) em formato binário conforme RFC 3161
* O carimbo de tempo emitido
* O certificado da autoridade que emitiu o carimbo de tempo
* Quais provedores foram utilizados para prover o carimbo de tempo e detalhes sobre a resposta de cada provedor, como tempo de resposta e código de retorno
Adicionalmente, é possível obter as mensagens do protocolo TSP -- solicitações (*timestamp queries*) e respostas (*timestamp replies*) -- diretamente no
armazenamento de blobs do sistema:

Para facilitar a consulta, as mensagens são agrupadas em pastas por ano, mês e dia, permitindo por exemplo obter com facilidade diretamente do storage todas as
mensagens de um determinado mês. Para cada solicitação há dois arquivos, conforme detalhado abaixo:
```text
tspmessages/2024/03/05/104710551-018c3515315743f8b3db6ebd9bcaa8c3.tsq
─────┰──── ────┰──── ─┰─
│ │ └──── timestamp query (solicitação)
│ └──────────────────────────────────────────── horário (10:47:10.551)
└────────────────────────────────────────────────────── data (2024-03-05)
tspmessages/2024/03/05/104710551-018c3515315743f8b3db6ebd9bcaa8c3.tsr
─────┰──── ────┰──── ─┰─
│ │ └──── timestamp reply (resposta)
│ └──────────────────────────────────────────── horário (10:47:10.551)
└────────────────────────────────────────────────────── data (2024-03-05)
```
> **Observação**
>
> As datas e horas presentes nos nomes de arquivos de mensagens de protocolo TSP estão no fuso padrão do sistema (configuração `General:DefaultTimeZone`)
## Veja também
* [Guia de Operação](https://docs.lacunasoftware.com/articles/rest-pki/core/operation)
# [Solicitando carimbos de tempo ao Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/requesting-timestamps)
Você pode utilizar uma instância do [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core) como provedor de carimbos de tempo
fornecidos através do protocolo TSP (*Timestamp Protocol* - RFC 3161) ou API REST.
Para isso, siga os passos abaixo para obter as credenciais de acesso:
1. Autentique-se na interface de gerenciamento da sua instância (ex: *https://restpkicore.suaempresa.com*)
1. Clique em **Aplicações** no menu lateral, em seguida em **Adicionar**
1. Preencha um nome e selecione uma organização (a sua instância provavelmente tem uma única subscription, então selecione-a)
1. Marque o papel **Operador**
1. Clique em **Criar**
1. Clique em **Chaves**, depois em **Adicionar**
1. Preencha alguma descrição e, no campo *Expiração*, escolha **Nunca expira**
1. Clique em **Criar**
1. Copie a **chave de API** gerada (esse valor não pode ser recuperado posteriormente)
> **Observação**
>
> Para solicitar carimbos à instância SaaS do Rest PKI Core, o endereço da interface de gerenciamento é [https://core.pki.rest/](https://core.pki.rest/)
Em seguida, utilize uma das formas abaixo para solicitar um carimbo de tempo.
## Comunicação via *Timestamp Protocol*
Utilize os parâmetros abaixo para solicitar um carimbo via TSP:
* Protocolo: HTTPS
* Method: POST
* Url: `https://restpkicore.suaempresa.com/tsp`
* Autenticação: header HTTP `Authorization: ApiKey SUA_CHAVE_DE_API`
> **Observação**
>
> Caso seja necessário especificar um identificador de plano, adicione um sufixo `/PLANO` à URL, o seja `https://restpkicore.suaempresa.com/tsp/PLANO`
Para solicitar carimbos à instância SaaS do Rest PKI Core, é preciso especificar um dos seguintes planos:
* Carimbo de tempo ICP-Brasil: `PkiBrazil` (ou `a402df41-8559-47b2-a05c-be555bf66310`)
* Carimbo de tempo para testes: `LacunaTest`
> **Observação**
>
> O custo por carimbo de tempo varia de acordo com o plano escolhido
## Comunicação via API REST
Utilize os parâmetros abaixo para solicitar um carimbo via API REST:
* Url: `https://restpkicore.suaempresa.com/api/tsp` (opcionalmente com sufixo `/PLANO`)
* Method: POST
* Request headers
* `Authorization` : `ApiKey SUA_CHAVE_DE_API`
* `Content-Type` : `application/json`
* Request
* `algorithm` : nome do algoritmo (veja algoritmos suportados abaixo)
* `value` : valor do hash a ser carimbado, codificado em Base64 **ou**
* `hexValue` : valor do hash a ser carimbado, codificado em hexadecimal (*case insensitive*)
* Response
* `encodedValue` : carimbo de tempo, codificado em Base64
* `info` : informações sobre o carimbo de tempo
> **Observação**
>
> A URL da API REST é muito similar à URL para requisições via TSP, a diferença sendo a presença do segmento `/api/`
A [referência da API](https://core.pki.rest/swagger) lista essa rota como `POST /api/tsp/{identifier}`, em que `identifier` é o plano. O segmento do plano é
opcional nas duas URLs: sem ele, o Rest PKI Core usa o plano padrão da instância; por isso, na instância SaaS, sempre o informe.
Exemplo de request com algoritmo SHA-256 e hash em Base64:
```json
{
"algorithm": "SHA256",
"value": "L80RIvFw2jwpiObzFQN+tuEQQ4E9qnGeYYYd+4DrM8M="
}
```
Exemplo de request com algoritmo SHA-1 e hash em hexadecimal:
```json
{
"algorithm": "SHA1",
"hexValue": "3ade2832feafe081f246aa2fb89cb9c7929ac911"
}
```
## Algoritmos suportados
Atualmente, os seguintes algoritmos são suportados:
* SHA-1
* TSP: 1.3.14.3.2.26
* API: "SHA1"
* SHA-256
* TSP: 2.16.840.1.101.3.4.2.1
* API: "SHA256"
**Demonstrações e exemplos**
- Demonstração: [Timestamp a PDF](https://try.lacunasoftware.com/pt/pki-suite/timestamp-a-pdf)
# [Histórico de versões - Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/changelog)
### 5.0.4 (2026-09-28)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-930] Política CAdES AD-RT sem LCRs não estava registrada (`SignaturePolicyNotFound`)
* [RPNG-933] Renotarização e inspeção de XML inconsistente com a própria assinatura retornavam HTTP 500 em vez de `InvalidXml`
### 5.0.3 (2026-09-25)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-935] Novo campo `archiveTimestamps` nas respostas de abertura e inspeção de assinaturas
* Correções de bugs
* [RPNG-935] Assinaturas XML retornavam `timestamps` nulo na abertura e inspeção
### 5.0.2 (2026-09-21)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-910] Dashboard de biometrias: campos de prova de vida 2D ocultos quando a verificação está desativada e exibição das últimas capturas de frente e verso do documento
* Correções de bugs
* [RPNG-911] Uploads segmentados (multipart) falhavam com HTTP 500 no Azure Blob Storage desde a 5.0.0
### 5.0.1 (2026-09-21)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-908] Exibição dos parâmetros e resultados da sessão de vídeo identificação no dashboard de biometrias
* Melhorias
* [RPNG-894] Troca de "Certidão" por "documento" na tela de validação de documentos
* Correções de bugs
* [RPNG-807] Transações de conversão e validação de PDF/A realizadas no preparo da assinatura não eram registradas
### 5.0.0 (2026-09-14)
> **Aviso**
>
> Veja [Atualizando o Rest PKI Core da versão 4.x para 5.0](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/update-50)
Atualiza modelo do banco de dados: sim
* Novas funcionalidades
* [RPNG-868] Opção de criar sessões de assinatura apenas com certificados em nuvem, pulando a instalação e seleção do Web PKI
* Melhorias
* [RPNG-639] Bibliotecas cliente passam a informar sua versão, registrada nas transações e sessões
* [RPNG-648] Melhoria na consistência das telas de configurações da subscription
* [RPNG-650] Atualização do ASP.NET Core para a versão 10.0
* [RPNG-686] Nova tela de demonstrações, com diálogo de parâmetros para as demonstrações de biometria
* [RPNG-730] Registro da versão do sistema nas sessões de biometria e de assinatura
* [RPNG-886] Atualização do Browser SDK da FaceTec
* [RPNG-888] Atualização do SDK FortFace para a versão 2.6.0
* Correções de bugs
* [RPNG-651] Logs de erro das sessões de biometria enviados pelo navegador eram gravados vazios
* [RPNG-793] Tipo de evento sem tradução na lista de eventos do dashboard
* [RPNG-874] Cores de marca visual em PDF com valores fora da faixa causavam erro 500 em vez de 400
### 4.5.9 (2026-09-11)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-886] Atualização do Browser SDK da FaceTec
### 4.5.8 (2026-09-10)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-836] Atualização do Browser SDK da FaceTec
* [RPNG-878] Vídeo identificação: melhoria no tratamento do verso do documento na última tentativa de captura
* Correções de bugs
* [RPNG-879] Verificação do nome na MRZ reprovava documentos com primeiros nomes abreviados
### 4.5.6 (2026-09-01)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-870] Melhorias da Vídeo identificação
### 4.5.5 (2026-08-31)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-843] Filtro por subscription na listagem paginada de subjects biométricos (bibliotecas cliente)
### 4.5.4 (2026-08-27)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-866] Vídeo identificação rejeitando documentos válidos cujo número esperado está nos dados opcionais da MRZ
### 4.5.3 (2026-08-26)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-827] Novos contextos de segurança padrão "PKI Brazil with e-Notariado" e "PKI Brazil with Gov.BR and e-Notariado"
* [RPNG-828] Adicionar possibilidade de visualizar a raiz confiável padrão utilizada na assinatura na resposta da validação de assinatura (habilitado por instalação)
* Correções de bugs
* [RPNG-784] [RPNG-864] Problema na captura de passaportes na vídeo identificação
### 4.4.4 (2026-08-18) [patch]
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-831] Erro ao visualizar certificado de raiz confiável em contextos de segurança System Admin
### 4.5.2 (2026-08-17)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-837] Listagem paginada de biometric subjects falhava ao ser chamada com parâmetros de query string
### 4.5.1 (2026-08-17)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-827] Contexto de segurança padrão E-Notariado (habilitado por chave de ativação)
* Correções de bugs
* [RPNG-830] Log via Serilog não funcionava na imagem com OpenTelemetry habilitado
* [RPNG-831] Erro ao visualizar certificado de raiz confiável em contextos de segurança System Admin
* [RPNG-832] Erro registrado no log de boot ao inicializar chaves da FaceTec em instâncias sem biometria configurada
### 4.5.0 (2026-08-06)
Atualiza modelo do banco de dados: sim
* Novas funcionalidades
* [RPNG-369] Atualização major do SDK de captura da FaceTec (v10)
* [RPNG-688] Reformular a validação de documentos no id-scan em um pipeline configurável por etapas, com dicas contextuais nas telas de nova tentativa
* [RPNG-780] Adicionar detalhes contextuais (número do documento, países aceitos, tipo de documento esperado) nas telas de falha do id-scan; corrigir textos em espanhol do SDK da FaceTec
* [RPNG-786] Adicionar painéis de depuração da tentativa FaceTec (dados do id-scan e decisão) ao dashboard de biometria
* [RPNG-803] Adicionar download e regeneração sob demanda do reporte em PDF da vídeo identificação no dashboard
* Melhorias
* [RPNG-656] Adicionar opção "ilimitado" aos limites de transação por subscription e por plano
* [RPNG-738] Melhorar a precisão dos campos do documento persistidos no id-scan, usando a tentativa efetivamente utilizada na aceitação
* [RPNG-797] Reporte em PDF da vídeo identificação agora mostra os resultados de validação por lado do documento capturado
* [RPNG-798] Adicionar indicador na API de status da sessão para quando um documento capturado for aceito com um critério de validação flexibilizado
* [RPNG-801] Adicionar opção por sessão para permitir a flexibilização de uma verificação de captura no id-scan
* Correções de bugs
* [RPNG-776] Corrige erros de API retornados como um 500 genérico em vez do modelo de erro apropriado
* [RPNG-789] Sessões de biometria falhavam ao iniciar quando o limite de transação do plano estava configurado como ilimitado
* [RPNG-809] Configuração da duração do cache de limite de transações não era aplicada, sempre utilizando o valor padrão
* [RPNG-823] Resultado de correspondência facial com o documento era reportado a partir da configuração, e não do resultado real da verificação
### 4.4.3 (2026-08-05)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-820] Suporte a blob storage no Google Cloud Platform (GCP)
* Melhorias
* [RPNG-822] Registro dos detalhes da exception (message e stack trace) nos spans de erro do OpenTelemetry
### 4.3.4 (2026-08-05) [patch]
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-822] Registro dos detalhes da exception (message e stack trace) nos spans de erro do OpenTelemetry
### 4.3.3 (2026-08-05) [patch]
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-820] Suporte a blob storage no Google Cloud Platform (GCP)
### 4.4.2 (2026-07-28)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-776] Corrige erros de API retornados como um 500 genérico em vez do modelo de erro apropriado
### 4.4.1 (2026-07-20)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-785] Gravação de vídeo na Identificação em Vídeo gerava um arquivo vazio no Safari/iOS
### 4.4.0 (2026-07-20)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-771] Captura de geolocalização best-effort nas sessões de biometria
* Melhorias
* [RPNG-695] Adicionar tradução dos motivos de falha nas sessões de biometria
* Correções de bugs
* [RPNG-701] Dados do documento apareciam em sessões sem ID Scan
### 4.3.2 (2026-07-13)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-770] Requisições de autenticação por certificado repetidas não eram reportadas com um erro distinto de reuso de nonce
### 4.3.1 (2026-07-08)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-762] Adicionar configuração para desativar a taxa mínima de dados do corpo da requisição
* Melhorias
* [RPNG-765] Atualizar para o SPA 7.3, adicionando telemetria de desempenho de runtime ao Microsoft Application Insights
* Correções de bugs
* [RPNG-766] Corrigir um erro raro que podia ocorrer ao usar as APIs de autenticação legadas
### 4.2.6 (2026-07-07) [patch]
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-762] Adicionar configuração para desativar a taxa mínima de dados do corpo da requisição
### 4.1.7 (2026-07-07) [patch]
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-762] Adicionar configuração para desativar a taxa mínima de dados do corpo da requisição
### 4.0.6 (2026-07-07) [patch]
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-762] Adicionar configuração para desativar a taxa mínima de dados do corpo da requisição
### 3.4.4 (2026-07-07) [patch]
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-762] Adicionar configuração para desativar a taxa mínima de dados do corpo da requisição
### 4.3.0 (2026-06-25)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-323] [RPNG-681] [RPNG-682] [RPNG-683] Adicionar captura de geolocalização nas sessões de biometria
* [RPNG-419] Adicionar suporte ao Lacuna FaceTecServer 3.0+
* [RPNG-503] Configurações da página de sessão biométrica
* [RPNG-574] Configurações de limites de transação por subscription (SysAdmin)
* [RPNG-594] Páginas de configuração de identificações biométricas
* [RPNG-610] Adicionar botão para remover a imagem da logo
* [RPNG-623] [RPNG-711] Adicionar botão de resetar para os padrões do sistema em cada seção das configurações
* Melhorias
* [RPNG-389] [RPNG-722] Otimizar listagem de dados utilizando paginação por cursor
* [RPNG-501] Exibir os dados de OCR do FaceTec ID Scan no dashboard
* [RPNG-535] Aplicar limites de transação para todos os tipos de transação
* [RPNG-605] [RPNG-679] Melhorar APIs do dashboard
* [RPNG-613] Adicionar traduções para falhas de cadastro biométrico e do cadastro em lote
* [RPNG-620] Adicionar informações de PDF/A na validação pública de documentos
* [RPNG-625] Adicionar nomes completos dos novos temas
* [RPNG-641] Atualizar o FaceTec browser SDK para v9.7.115
* [RPNG-649] Melhorar o botão que informa a subscription atual
* [RPNG-689] Adicionar tradução para a frase "Todas as organizações"
* Correções de bugs
* [RPNG-538] Erro "User not found" ao abrir a tela de detalhes do usuário
* [RPNG-644] Filtro por aplicação na tabela de transações não funcionava
* [RPNG-655] Transação duplicada na conclusão de autenticação com certificado (legado)
* [RPNG-702] FaceMaps antigos não eram excluídos após autenticação biométrica
### 4.2.5 (2026-06-24)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-716] Atualizações dos SDKs da FaceTec
### 4.2.4 (2026-04-28)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-636] Adicionar configuração para desabilitar upload na página pública de validação de documentos
### 4.2.3 (2026-04-21)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-624] Erro no processamento de Liveness quando há baixa qualidade da imagem na captura anterior
### 4.2.2 (2026-04-18)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-622] Problemas de compatibilidade na configuração de limite de upload da API
### 4.2.1 (2026-04-18)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-621] Adicionar SubjectIdentifier para sessões de captura de documento de identidade
### 4.2.0 (2026-04-16)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-438] [RPNG-611] Adicionar suporte a conversão e validação de PDF/As
### 4.1.6 (2026-04-16)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-616] Página de configurações da subscription lança erros depois de ter sido fechada
### 4.1.5 (2026-04-16)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-617] Melhorar matcher de número de identidade das vídeo identificações
### 4.1.4 (2026-04-16)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-615] Adicionar rota para obter detalhes de sessões FortFace
### 4.1.3 (2026-04-15)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-612] Aumentar limite máximo configurável para tamanho de uploads
### 4.1.2 (2026-04-09)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-607] Política Adobe Reader para geração de assinatura
* Melhorias
* [RPNG-608] Atualização PKI SDK para [2.22.3](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-22-3) com correção do loop de validação de TSL
### 3.7.2 (2026-04-09) [patch]
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-608] Atualização PKI SDK para [2.22.3](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-22-3) com correção do loop de validação de TSL
### 3.4.3 (2026-04-07) [patch]
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-608] Atualização PKI SDK para [2.22.3](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-22-3) com correção do loop de validação de TSL
### 4.1.1 (2026-04-06)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-606] Atualização do SDK da Fortface para v2.4.3
### 4.1.0 (2026-04-01)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-302] [RPNG-599] [RPNG-602] Sessão de identificação biométrica (ABIS)
* [RPNG-440] [RPNG-569] [RPNG-580] [RPNG-585] [RPNG-588] PNG-589 RPNG-595 Importação de biometrias em lote
* [RPNG-579] [RPNG-584] [RPNG-604] Autenticação facial com fallback para provedor de identidade externa
* [RPNG-568] [RPNG-575] Adicionar lista de padrões de SubjectIdentifiers aceitos para Subscription
* Melhorias
* [RPNG-544] Mapeamento de TransactionLimits para todas as operações que envolvem Carimbo de Tempo
* [RPNG-555] SysAdmin - Tela de permissões da subscription
* [RPNG-558] Informação de restrição na listagem e detalhes de planos de carimbo de tempo
* [RPNG-559] Dashboard - Melhorias nas informações de FaceTec IdScan
* [RPNG-587] Adicionar no detalhe do bio subject: data de exclusão
* [RPNG-591] Adicionar "null expected" à chamada de get subject by identifier
* [RPNG-592] Melhorar traduções da informação de sessão FortFace
* [RPNG-603] Atualizações dos SDKs da FaceTec
* [RPNG-567] Listagem paginada de Subjects da subscription via ClientLib
### 4.0.5 (2026-03-20)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-547] [RPNG-582] Adicionar suporte ao sistema de CNPJs alfanuméricos
### 3.7.1 (2026-03-20) [patch]
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-547] [RPNG-582] Adicionar suporte ao sistema de CNPJs alfanuméricos
* [RPNG-540] Configuração de sistema para usar apenas a língua padrão
* Correções de bugs
* [RPNG-533] TypeError durante a listagem de certificados do Web PKI
### 4.0.4 (2026-03-18)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-576] Atualização do FortFace SDK para v2.4.2
### 4.0.3 (2026-03-16)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-566] Problemas com atualização do FortFace SDK para v2.4.1
### 4.0.2 (2026-03-12)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-546] Dashboard - Adicionar visualização de detalhes de transações registradas com TypeDiscriminator do TransactionService
* [RPNG-570] Atualização do FortFace SDK para v2.4.1
* Correções de bugs
* [RPNG-566] Rotas de completar sessões exigindo subscription
### 4.0.1 (2026-03-10)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-563] Problema com injeção de dependências do BusinessService
### 4.0.0 (2026-03-09)
> **Aviso**
>
> Veja [Atualizando o Rest PKI Core da versão 3.x para 4.0](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/update-40)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-273] [RPNG-519] Fluxo de faturas para uma subscription
* [RPNG-347] [RPNG-497] Melhorias nas transações FaceTec
* [RPNG-458] Dashboard de biometria - Listagem de sessões do cadastro biométrico
* [RPNG-461] Serviço de limite de transações por tipo/mês
* [RPNG-478] Adicionar transações para sessões FortFace
* [RPNG-484] Opção de utilizar sessões de biometria com retorno de URL assim como nas de assinatura
* [RPNG-488] Adicionar configuração de Timeout para sessão de biometria
* [RPNG-505] Lista de ReturnUrl aceitas para BioSession
* [RPNG-511] Vídeo Identificação - Receber valores esperados do documento \(tipo/número\)
* [RPNG-517] Permissionamento por subscription para TimestampTiers
* Melhorias
* [RPNG-270] Interface com listagem das sessões de assinatura
* [RPNG-405] Melhorias na tela inicial das sessões de biometria e customizações
* [RPNG-430] Marcas de carimbo de tempo em espanhol
* [RPNG-435] Dashboard de Biometria - Adicionar visualização de imagens das sessões biométricas
* [RPNG-443] [RPNG-447] [RPNG-448] [RPNG-450] Atualizações do Angular para versão 19
* [RPNG-471] Botão para carregar todas as imagens nos detalhes da sessão de biometria
* [RPNG-472] Salvar informação sobre Agent que criou a BioSession
* [RPNG-474] Campos Swagger para sessões de biometria
* [RPNG-477] Adicionar link da sessão FaceTec no detalhe da transação
* [RPNG-481] Adicionar botão de copiar chave de API gerada
* [RPNG-486] Apresentar mensagem de erro amigável nas sessões de biometria
* [RPNG-490] Customização das cores e logo da sessão de biometria por subscription
* [RPNG-507] Informações da vídeo identificação via API
* [RPNG-508] [RPNG-521] [RPNG-531] [RPNG-541] [RPNG-548] Melhorias de layout das telas de administração
* [RPNG-509] Melhorias nos templates OCR para passaportes
* [RPNG-513] Dashboard - Assistir ao vídeo gravado na vídeo identificação
* [RPNG-468] [RPNG-515] [RPNG-560] Atualizações dos SDKs da FaceTec
* [RPNG-536] Melhorias de traduções dos botões de "Encerrar sessão"
* [RPNG-537] Melhoria na coleta de logs de erro para o SDK da FortFace
* [RPNG-540] Configuração de sistema para usar apenas a língua padrão
* Correções de bugs
* [RPNG-258] Campos do PadesSignaturePostRequestBase sendo ignorados
* [RPNG-487] Sessão de biometria começa como "Falha" no dashboard
* [RPNG-512] Vídeo Identificação - Correção da condição de sucesso na vídeo identificação
* [RPNG-518] Corrigir coleta do Agent para as transações de BioSession
* [RPNG-523] Vídeo Identificação - Problemas na finalização do FaceTec IdScan
* [RPNG-525] Vídeo Identificação - Problema ao gravar/enviar vídeo
* [RPNG-527] Sessões ficam presas em "loading" em dispositivos IOS 26.2
* [RPNG-533] TypeError durante a listagem de certificados do Web PKI
* [RPNG-534] Erro 500 em requisições utilizando métodos inválidos (POST) em rotas do ClientApp
* [RPNG-539] Formulário de configuração da Subscription marcado com unsaved changes mesmo depois de reset
* [RPNG-542] Telas não atualizam ao trocar de subscription
### 3.7.0 (2025-11-26)
Atualiza modelo do banco de dados: **sim**
* Melhorias
* [RPNG-453] Melhorar fotos de enrollment com liveness FortFace
### 3.6.2 (2025-11-24)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-394] Dashboard de Biometria - Tela de detalhamento de BioSubjects
* Melhorias
* [RPNG-428] Atualizar SDKs da FortFace
* [RPNG-454] Adicionar InteractionMethod no BioSessionModel para BioDashBoard
### 3.6.1 (2025-11-20)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-320] Carregamento de dados de sessões FaceTec legadas
### 3.6.0 (2025-11-20)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-387] Adicionar tags para transações
* [RPNG-445] Dashboard - Adicionar sessões de datavalid nos detalhes da sessão de biometria
* [RPNG-460] Adicionar ferramentas para extração de fotos das sessões de liveness FaceTec
* Melhorias
* [RPNG-446] Dashboard - Adicionar campos faltantes de FaceTecSession
### 3.5.2 (2025-11-18)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-457] Problemas com configuração de provedor de identidade externa da Subscription
### 3.5.0 (2025-11-17)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-336] [RPNG-397] Ferramentas para importação de dados FaceTec
* [RPNG-392] Dashboard de Biometria - Tela de detalhamento da sessão de biometria
* [RPNG-395] Dashboard de Biometria - Filtros na listagem de sessões
* [RPNG-427] Carimbo de tempo para vídeo identificação
*
* Melhorias
* [RPNG-157] Adicionar campo para inserir Api-Key no swagger
* [RPNG-274] Melhorar tratamento de PDFs protegidos por senha
* [RPNG-390] Logar eventos de frontend dos SDKs FortFace e FaceTec
* [RPNG-406] Refatoração de PolicyWrappers com base no PKI Express
* [RPNG-423] Ativar descrição de campos no Swagger
* [RPNG-429] Persistência de informaçõe sadicionais das sessões FaceTec no banco de dados
* [RPNG-431] [RPNG-436] Vídeo Identificação - Melhoria no PDF de result report
* [RPNG-444] Opção de gerar result-data para sessões biométricas de cadastro e autenticação
* Correções de bugs
* [RPNG-357] Não existe mensagem de erro amigável quando outro app está usando a câmera (FortFace)
* [RPNG-409] Problemas na customização de cores FaceTec IdScan
### 3.4.0 (2025-10-28)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-351] [RPNG-352] [RPNG-359] [RPNG-353] [RPNG-364] [RPNG-391] [RPNG-407] [RPNG-422] [RPNG-426] Adicionar sessão de vídeo identificação
* [RPNG-360] Permitir customização estática da Home Page (por instância)
* [RPNG-379] Implementar gerenciamento de configurações de PDF e Contexto de Segurança por Subscription
* [RPNG-381] Adicionar API para renotarização de diplomas
* [RPNG-393] Dashboard de Biometria - Adicionar tela de listagem de cadastros
* Melhorias
* [RPNG-151] Adicionar traduções para espanhol
* [RPNG-414] Atualização PKI SDK para [2.21.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-21-0)
* Correções de bugs
* [RPNG-285] Ajustes de permissões
* [RPNG-388] Erro de constraint duplicada ao salvar certificados
### 3.3.0 (2025-10-16)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-184] Cadastro de ACs (Autoiridades Certificadoras) intermediárias
* [RPNG-324] Registro de thumbprint de chave pública das sessões FaceTec
* [RPNG-346] Sessões biométricas de cadastro e autenticação por foto
* [RPNG-358] Sessão de correspondência entre documento de identificação com face
* [RPNG-374] Verificação de identidade facial com provedores externos (FortFace/Datavalid)
* [RPNG-377] Sessões biométricas de cadastro e autenticação utilizando captura de face com prova de vida FortFace
* [RPNG-383] [RPNG-382] Adicionar tela de configurações de biometria da subscription
* [RPNG-385] Tela de listagem resumida de sessões biométricas com visualização de imagens
* [RPNG-404] Adicionar configuração para aceitar imagens de baixa resolução na API de autenticação facial
* Melhorias
* [RPNG-312] [RPNG-314] [RPNG-372] [RPNG-375] [RPNG-384] [RPNG-343] Melhorias na modelagem de entidades relacionadas a biometria
* [RPNG-371] Melhorar layout das demonstrações de sessões biométricas
* [RPNG-373] Melhorar layout das sessões biométricas em dispositivos móveis
* [RPNG-399] Adicionar resources do FaceTec browser SDK v9.7.83
* Correções de bugs
* [RPNG-376] Inconsistência na configuração do limite de tentativas de liveness nas sessões biométricas com FaceTec
*
### 3.2.1 (2025-08-27)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-367] Atualizar certificados da cadeia de testes v3
* Correções de bugs
* [RPNG-365] Garantir build com última imagem do AspNet
### 3.2.0 (2025-08-22)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-319] QR Code para sessões de biometria
* [RPNG-329] Detecção de oclusão de face nas sessões de prova de via via FortFace
* Melhorias
* [RPNG-325] Aplicar de temas nas sessões de biometria
* [RPNG-331] Atribuir sessões biométricas implicitas para chamadas de API FaceTec
* [RPNG-332] Recortar imagem do resultado da prova de vida via FortFace
* [RPNG-354] Adicionar resources do FaceTec browser SDK v9.7.75
* Correções de bugs
* [RPNG-355] Erro `BadRequest` ao tentar utilizar AWS S3 em alguns cenários
### 3.1.1 (2025-07-24)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-340] Adicionar resources do FaceTec browser SDK v9.7.72
* Correções de bugs
* [RPNG-341] Imagem para ARM64 não está escutando na porta 80 após atualização para .NET 8
### 3.1.0 (2025-07-24)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-297] Sessões de biometria
### 3.0.1 (2025-06-25)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-316] Comando/Job para realizar teste de integridade de documentos
* Melhorias
* [RPNG-284] Adicionar resources do FaceTec browser SDK v9.7.63 e v9.7.66
* Correções de bugs
* [RPNG-315] Erro `BlobNotFoundException` em certos blobs da pasta `signedfiles`
### 3.0.0 (2025-06-16)
> **Aviso**
>
> Veja [Atualizando o Rest PKI Core da versão 2.x para 3.0](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/update-30)
Atualiza modelo do banco de dados: **sim**
* Melhorias
* [RPNG-275] Atualizado ASP.NET Core para versão 8.0
* [RPNG-292] Resolver vulnerabilidades conhecidas (para mais informações veja a [Verificação de vulnerabilidade 25.04](https://docs.lacunasoftware.com/articles/rest-pki/core/on-premises/vulnerabilities#verificação-de-vulnerabilidades-2504))
* [RPNG-295] Sinalizar para mecanismos de busca que páginas de validação de documentos não devem ser indexadas
* [RPNG-299] Melhorar exibição do carimbo de tempo na tela de validação
* [RPNG-301] Criar imagem "distroless" com superfície de ataque reduzida
* Correções de bugs
* [RPNG-293] Marcas do tipo QRCode causam erro 500 na API de adicionar marcas se executando em Linux
* [RPNG-296] Detalhes da validação do certificado na página de validação de documentos ficam em inglês mesmo selecionando português
### 2.5.0 (2025-04-11)
Idêntica a: 2.5.0 RC 7 (2025-04-11)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-276] APIs para realização de prova de vida (*liveness*) com FaceTec
* [RPNG-279] APIs para realização de digitalização de documento de identificação (*Id Scan*) com FaceTec
* [RPNG-280] Demonstração de prova de vida com FaceTec
* Melhorias
* [RPNG-287] Suporte a uso de schemas de banco de dados personalizados
* [RPNG-289] Suporte a plataforma ARM64 em Docker
* Correções de bugs
* [RPNG-288] Erro *An error has occurred while processing your authentication \(blank email address\). Please try again.*
* Alterações para variantes específicas
* [RPNG-286] Marcas de validação específicas para caso paisagem \(variante *onr*\)
### 2.4.5 (2025-04-04)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-277] Coluna `DateCompleted` não é preenchida quando uma sessão de assinatura é completada
### 2.4.4 (2024-10-02)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-271] Melhorar desempenho de chamadas simples repetidas muitas vezes por clientes
### 2.4.3 (2024-09-25)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-291] Problemas de retrocompatibilidade na API de adicionar marcas a PDFs
### 2.4.2 (2024-07-26)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-269] Erro 500 ao realizar assinar de PDF com política `PkiBrazil.AdrTempo`
### 2.4.1 (2024-07-25)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-290] Erro de compatibilidade com SQL Server 2012 \(uso de função `TRIM()`\)
### 2.4.0 (2024-07-25)
Idêntica a: 2.4.0 RC 1 (2024-06-15)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-262] Configuração `Oidc:AuthorityBackchannelUrl` para situações em que o GrantID tem uma URL específica para comunicação de backend para backend
* Melhorias
* [RPNG-265] Melhorar especificação da assinatura a ser executada com a chave privada do assinante nas APIs
* [RPNG-266] Políticas para validar assinaturas feitas no Gov.BR
* Alterações para variantes específicas
* [RPNG-264] Metadados do documento não aparecem na página de validação \(variante *onr*\)
### 2.3.0 (2024-06-06)
Idêntica a: 2.3.0 RC 1 (2024-05-17)
Atualiza modelo do banco de dados: **sim**
* Alterações para variantes específicas
* [RPNG-259] CNS e Cartório por subscription \(variante *onr*\)
### 2.2.2 (2024-05-04)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-261] Erro NullReferenceException ao solicitar carimbo via TSP
### 2.2.1 (2024-05-01)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-260] URLs relativas de upload em chamadas à API ainda não são aceitas em Linux/Docker
### 2.2.0 (2024-04-30)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-241] Registro de transações de API
* [RPNG-225] API para adicionar carimbo em arquivos PDF
* [RPNG-214] Configurações por subscription de cultura, fuso horário e formato de data/hora (usadas na representação visual de assinaturas PDF)
* Melhorias
* [RPNG-236] Alternância (*round-robin*) de provedores de carimbo com definição opcional de prioridades
*
* [RPNG-239] Atualizar Angular para versão 17
* [RPNG-248] Aceitar URLs relativas de upload em chamadas à API
* [RPNG-255] Exibir na home page da subscription eventos, solicitações de carimbo de tempo e transações
* [RPNG-256] Melhorar e separar em item de menu específico as demonstrações de uso das APIs
* [RPNG-257] Apagar mensagens antigas de TSP apenas caso explicitamente configurado
* Correções de bugs
* [RPNG-235] Sistema não está tentando obter carimbo de tempo novamente em caso de erro
* [RPNG-237] Job para completar assinatura CMS está dando erro
* [RPNG-245] Parâmetros inválidos de representação visual estão gerando resposta 500 ao invés de 422
* [RPNG-247] URLs inválidas no `ResourceContentOrReference.Url` causam erro 500
* [RPNG-250] Exceções transformadas em ApiException não são logadas corretamente
* [RPNG-252] Filtro por data não funcionando na tela de "Detalhes do provedor de carimbo de tempo"
* [RPNG-253] Pedido de carimbo de tempo não registra duração em caso de erro no provedor
* [RPNG-254] Consulta a chamadas de provedor de carimbo de tempo não utiliza o índice adequado
### 2.1.3 (2024-04-10)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-240] Usar timestamp tier padrão no caso de uma política com carimbo ser usada em conjunto com um contexto de segurança personalizado sem tier de timestamp definido
### 2.1.2 (2024-03-14)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-231] Inconformidades nos códigos de retorno declarados no Swagger
### 2.1.1 (2024-01-24)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-234] Criar rota `/api/system/manifest`
* Melhorias
* [RPNG-228] Adicionar configuração para logar requisições HTTP
* Correções de bugs
* [RPNG-208] Propriedade `signatureResult.getFile().url` retorna uma URL absoluta (era relativa no Rest PKI clássico)
### 2.1.0 (2024-01-15)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-215] Configuração de proxy
* Melhorias
* [RPNG-216] Adicionar suporte a modo Standby para instalação em Azure App Service
* [RPNG-219] Exibir informações de certificado de atributo agregado às assinaturas
* [RPNG-224] Revisar swagger
* Correções de bugs
* [RPNG-222] Erro ao salvar contexto de segurança referenciando plano de carimbo de tempo
* [RPNG-226] Informações necessárias à finalização da assinatura são perdidas em caso de erro na finalização
* [RPNG-227] Erro "NotSupportedException" nas APIs de start Cades V1 e V2
* [RPNG-232] Aplicação está exigindo claim de CPF desnecessariamente
### 2.0.2 (2023-11-09)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-220] Ignorar erros de comunicação com o Amplia caso `AttributeCertificateInclusion` seja `Recommended`
### 2.0.1 (2023-11-08)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-217] Erro "CryptographicException: The payload was invalid" ao consultar sessões de assinatura antigas
### 2.0.0 (2023-10-30)
Atualiza modelo do banco de dados: **sim**
* Melhorias
* [RPNG-178] Permitir definição opcional de timestamp tier no contexto de segurança
* [RPNG-204] Anonimização de dados na tela de validação
* [RPNG-207] Melhorias ao gerenciamento de provimento de carimbos de tempo
* [RPNG-246] Filtro por data de início e fim na lista de chamadas ao provedor de carimbo de tempo
* Correções de bugs
* [RPNG-198] API de validação de assinaturas não respeita configuração `General:DefaultTimeZone`
### 1.13.1 (2023-10-17)
Atualiza modelo do banco de dados: não
* Alterações para variantes específicas
* [RPNG-212] Alterar texto da marca de validação \(variante *onr*\)
### 2.0.0 RC 16 (2023-09-12)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-160] Adicionar suporte às APIs de assinatura CADES antigas
* [RPNG-182] Histórico de timestamp tiers e providers
* [RPNG-206] Habilitar acesso via SSH na imagem de Docker
* Correções de bugs
* [RPNG-185] Arquivos de certificado não são aceitos no upload de raízes do gerenciamento de contexto de segurança
* [RPNG-200] Propriedade MessageDigest dos assinantes PDF/CMS não é preenchida em situações em que apenas o algoritmo é conhecido
* [RPNG-201] APIs de exploração de assinatura estão ignorando o parâmetro `SignaturePolicyId`
* [RPNG-209] Multipart upload não está funcionando
### 2.0.0 RC 8 (2023-06-30)
> **Aviso**
>
> Antes de atualizar para esta versão, garanta que sua licença do PKI SDK suporta versões lançadas até 2023-06-27.
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-192] Criar configuração para retornar exceções nas respostas de erro quando a API tiver sido chamada por uma aplicação
* Correções de bugs
* [RPNG-191] Respostas 422 não preenchem propriedade Detail
* [RPNG-193] PDFs assinados com padrão antigo \(não-PAdES\) causam erro nas APIs de abertura de assinatura
* [RPNG-194] PDFs protegidos por senha causam erro 500 \(deveriam causar 422 com código ProtectedPdfError\)
* [RPNG-195] PDFs corrompidos causam erro 500 \(deveriam causar 422 com código InvalidPdf\)
* [RPNG-196] Erro "Wrong last certificate on Pades signature"
### 2.0.0 RC 7 (2023-05-30)
> **Aviso**
>
> Antes de atualizar para esta versão, garanta que sua licença do PKI SDK suporta versões lançadas até 2022-10-19.
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-155] Adicionar suporte às APIs de autenticação antigas
* [RPNG-159] Adicionar suporte às APIs de assinatura de PDF antigas
* [RPNG-161] Adicionar suporte às APIs de assinatura de XML antigas
* [RPNG-162] Adicionar suporte às APIs de PdfMarking antigas
* [RPNG-177] Adicionar suporte a políticas de assinatura
* [RPNG-179] Agregar certificado de atributo automaticamente a assinaturas
* [RPNG-183] Adicionar suporte à API de validação de certificados legada
* Correções de bugs
* [RPNG-180] Assinatura com certificado em nuvem parou de funcionar após atualização para SPA 2.6 \(v1.13.0\)
* [RPNG-190] Erro "cannot access a closed file" usando blob storage tipo AwsS3
### 1.13.0 (2023-03-14)
Atualiza modelo do banco de dados: **sim**
* Melhorias
* [RPNG-153] Adicionar representação visual à API de assinatura
* [RPNG-172] Permitir blob storage em "S3 clones"
* [RPNG-169] Completar atualização para SPA 2.6
### 1.12.2 (2023-02-28)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-173] API de obter detalhes de um documento pelo ID não está permitindo acesso inter-contas em instâncias on-premises
### 1.12.1 (2023-01-05)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-164] Melhorar configuração em Linux
### 1.12.0 (2022-12-13)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-158] Adicionado suporte a PostgreSQL
* Melhorias
* [RPNG-156] Atualizado ASP.NET Core para versão 6.0
* Correções de bugs
* [RPNG-152] Não é possível adicionar raízes confiáveis na tela de criação do contexto de segurança
### 1.11.0 (2022-11-14)
Atualiza modelo do banco de dados: **sim**
* Melhorias
* [RPNG-154] Criar configuração de margens esquerda e superior na marcação de PDF
### 1.10.2 (2022-05-03)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-149] Permitir forçar política de assinatura de PDFs para padrão ICP Brasil
### 1.10.1 (2022-04-23)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-148] Erro ao utilizar contexto de segurança personalizado
### 1.10.0 (2022-04-23)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-2] API para autenticação com certificado digital
* Melhorias
* [RPNG-146] Parametrização de PDF marks
* [RPNG-147] Permitir configurar se PDFs serão marcados
### 1.9.0 (2022-03-29)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-41] API para validação de documentos fornecendo o arquivo
* [RPNG-141] Gerenciamento de contextos de segurança da interface web
### 1.8.0 (2022-02-25)
Atualiza modelo do banco de dados: **sim**
* Melhorias
* [RPNG-10] [Requisitos de certificados](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions/certificate-requirements) em sessões de assinatura
### 1.7.0 (2022-01-25)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-135] Assinatura CAdES detached
* [RPNG-44] Interface para acompanhar chamadas \(em especial falhas\) ao webhook e à API de validação de arquivos
* Melhorias
* [RPNG-51] Interface de configuração do tema e *assets* personalizáveis da conta
* Correções de bugs
* [RPNG-144] Imagens da sessão de assinatura ficam quebradas na volta do login com provedor de certificado em nuvem
### 1.6.0 (2021-12-08)
Atualiza modelo do banco de dados: não
* Novas funcionalidades
* [RPNG-129] Suporte a certificados em nuvem na sessão de assinatura
* Melhorias
* [RPNG-139] Melhorar informações fornecidas sobre o certificado do assinante no `SignatureSessionModel`
### 1.5.1 (2021-11-23)
Atualiza modelo do banco de dados: não
* Correções de bugs
* [RPNG-140] Erro ao tentar usar root password authentication
### 1.5.0 (2021-09-30)
Atualiza modelo do banco de dados: **sim**
* Novas funcionalidades
* [RPNG-132] Suporte a assinatura CAdES/CMS
* [RPNG-93] Sessão de assinatura com documentos pré-definidos
* Melhorias
* [RPNG-138] Exibir preview dos documentos sem marcas de validação
* [RPNG-136] Aplicar marcas em PDFs mesmo que a assinatura seja CAdES
* [RPNG-128] Melhorar uso dos theme-assets para permitir logos com diferentes proporções
* [RPNG-120] Tratar exceção de PDF inválido/corrompido
* Correções de bugs
* [RPNG-119] Padrões de nomes aceitos estão vindo do subscription cache
* Alterações para variantes específicas
* [RPNG-130] Adicionada variante *onr*
### 1.4.2 (2021-08-24)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-126] Permitir configuração da quantidade de background workers
* Correções de bugs
* [RPNG-125] Erro no upload segmentado
### 1.4.1 (2021-08-24)
Atualiza modelo do banco de dados: não
* Melhorias
* [RPNG-122] Melhorar texto de validação
* [RPNG-85] Ajustes solicitados ao input de chave de documento
* Correções de bugs
* [RPNG-124] Erros sob alta demanda
* [RPNG-123] Retries de processamentos de assinaturas falham após certo tempo transcorrido
### 1.4.0 (2021-08-11)
* Primeira versão disponível publicamente
# [Referência rápida do PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/quick-reference)
O essencial do PKI SDK em uma página, com links para os artigos completos.
## Para que serve
O PKI SDK é uma biblioteca .NET para operações de infraestrutura de chaves públicas: decodificar e validar certificados X.509 e criar e validar assinaturas CMS/CAdES, PDF/PAdES e XmlDSig/XAdES.
Ele roda dentro da sua aplicação .NET, no .NET Framework ou, pelo .NET Standard 2.0, no .NET Core (inclusive em servidores Linux); nas assinaturas web, o Web PKI faz a parte do navegador e o PKI SDK a parte do servidor.
| Alternativa | Quando é a melhor escolha |
| --- | --- |
| [PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference) | Seu backend não é .NET: o PKI Express cria assinaturas CAdES, PAdES e XAdES a partir de praticamente qualquer linguagem, hospedado nos seus próprios servidores Windows ou Linux. |
| [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/quick-reference) | Você prefere uma API REST a uma biblioteca: use-o como serviço em core.pki.rest ou numa instância própria, e ele hospeda a página em que os usuários assinam. |
| [Web PKI](https://docs.lacunasoftware.com/articles/web-pki/quick-reference) | Você precisa da parte do navegador: o Web PKI lista os certificados do usuário e assina com eles; ele complementa o PKI SDK nas assinaturas web remotas. |
| [Signer](https://docs.lacunasoftware.com/articles/signer/quick-reference) | Você quer um gerenciador de documentos e assinaturas pronto, como SaaS ou on-premises, em vez de construir o fluxo de assinatura por conta própria. |
## Instalação
| Linguagem | Pacote | Major atual | Registro |
| --- | --- | --- | --- |
| C# / .NET (NuGet) | `Lacuna.Pki` | 2 (mais recente: 2.22.7) | https://www.nuget.org/packages/Lacuna.Pki |
| C# / .NET, certificados em nuvem (opcional) | `Lacuna.Pki.BrazilTrustServices` | 1 (mais recente: 1.7.4) | https://www.nuget.org/packages/Lacuna.Pki.BrazilTrustServices |
Adicione o `Lacuna.Pki` pelo NuGet Package Manager (pacote **Lacuna PKI SDK**); a versão 2.22.7 tem como alvo o .NET Standard 2.0 e o .NET Framework 4.5, 4.6 e 4.7 ([Suporte a .NET Standard / .NET Core](https://docs.lacunasoftware.com/articles/pki-sdk/net-standard)).
Pacotes opcionais trazem integrações de armazenamento, de log e de armazenamento de nonces: `Lacuna.Pki.AzureConnector`, `Lacuna.Pki.AmazonConnector`, `Lacuna.Pki.EntityFrameworkConnector` e `Lacuna.Pki.NLogConnector`; veja [Pacotes nuget opcionais](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages) e o [Histórico de versões](https://docs.lacunasoftware.com/articles/pki-sdk/changelog).
## Configuração e autenticação
O PKI SDK roda no processo da sua aplicação e é habilitado por uma licença, um arquivo `LacunaPKILicense.config` ou uma string Base64, carregada na inicialização da aplicação.
Carregue-a de uma destas três formas ([Licenciamento](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/licensing)):
- Aplicações web (recomendado): copie o `LacunaPKILicense.config` para junto do `Lacuna.Pki.dll`, por exemplo com **Build Action** definido como **Content** e **Copy to Output Directory** como **Copy Always**.
- `Web.config` ou `App.config`: a chave de `appSettings` `LacunaPKI.LicensePath` (caminho do arquivo) ou `LacunaPKI.BinaryLicense` (licença em Base64).
- Aplicações desktop (recomendado): `PkiConfig.LoadLicense(byte[])` com a licença Base64 decodificada; `PkiConfig.LoadLicense(string)` recebe o caminho do arquivo.
```csharp
byte[] binLicense = Convert.FromBase64String("[given Base64 License]");
PkiConfig.LoadLicense(binLicense);
```
As demais configurações são propriedades da classe [`PkiConfig`](https://docs.lacunasoftware.com/api/Lacuna.Pki/PkiConfig), como `CrlDownloadTimeout`, `TimestampRequestTimeout`, `TempPath`, `PreemptiveCrlDownload` e `WebProxy` ([Configurações](https://docs.lacunasoftware.com/articles/pki-sdk/config)).
Toda validação recebe um `ITrustArbitrator`, que decide em quais raízes confiar: `TrustArbitrators.PkiBrazil`, `TrustArbitrators.Windows`, `TrustedRoots` ou vários deles combinados num `LinkedTrustArbitrator`.
Não há URL base a configurar: o SDK baixa online certificados de AC, LCRs (CRLs) e respostas OCSP ao montar e validar cadeias, e políticas com carimbo de tempo precisam de um `TimestampRequester` criado com a URI da sua carimbadora (autoridade de carimbo de tempo).
## Operações principais
Os exemplos são em C#, reduzidos a partir da documentação e do projeto ASP.NET MVC do PkiSuiteSamples, em que `Util.GetTrustArbitrator()` retorna o árbitro de confiança (trust arbitrator) usado (veja [Certificados e credenciais de teste](https://docs.lacunasoftware.com/articles/pki-sdk/quick-reference#certificados-e-credenciais-de-teste)).
### Decodificar e validar um certificado
Carregue um certificado com encoding BER, DER, Base64 ou PEM e valide-o segundo as raízes em que você confia: `PKCertificate.Decode` e `PKCertificate.Validate`. Veja [Carregando certificados](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/decoding) e [Validando certificados](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/validation).
```csharp
var cert = PKCertificate.Decode(pem);
var vr = cert.Validate(TrustArbitrators.PkiBrazil);
if (vr.IsValid) {
// the certificate is valid
}
Console.WriteLine(vr);
```
### Assinar um PDF com o Web PKI
A chave do usuário fica no navegador: o servidor prepara os bytes a assinar com `PadesSigner.GetToSignBytes`, o Web PKI assina o hash desses bytes (`readCertificate` e depois `signHash`) e o servidor conclui com `PadesSigner.SetPreComputedSignature`, guardando os dados de transferência (transfer data) entre as duas requisições. Veja [Assinatura com chave remota (assinatura no browser)](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/web-remote) e [Assinaturas padrão PAdES](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades).
```csharp
// Start: the certificate encoding was read in the browser by Web PKI
var padesSigner = new PadesSigner();
padesSigner.SetPdfToSign(userfilePath);
padesSigner.SetSigningCertificate(PKCertificate.Decode(model.CertContent));
padesSigner.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(Util.GetTrustArbitrator()));
toSignBytes = padesSigner.GetToSignBytes(out signatureAlg, out transferData);
var toSignHash = signatureAlg.DigestAlgorithm.ComputeHash(toSignBytes); // signed in the browser
// Complete: model.Signature is the signHash() output
padesSigner = new PadesSigner();
padesSigner.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(Util.GetTrustArbitrator()));
padesSigner.SetPreComputedSignature(model.Signature, transferDataContent);
padesSigner.ComputeSignature();
var signedPdf = padesSigner.GetPadesSignature();
```
### Assinar um PDF com uma chave no servidor
O PKI SDK é a forma recomendada de implementar [assinaturas de servidor](https://docs.lacunasoftware.com/articles/pki-guide/server-signatures) em .NET: carregue um `PKCertificateWithKey` (de um arquivo PKCS#12 com `Pkcs12CertificateStore.Load` ou do repositório de certificados do Windows com `WindowsCertificateStore`) e chame `PadesSigner.ComputeSignature` diretamente. Veja [Certificados com chave privada associada](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/certs-with-key).
```csharp
var store = Pkcs12CertificateStore.Load(certContent, "1234");
var certWithKey = store.GetCertificatesWithKey().First();
var padesSigner = new PadesSigner();
padesSigner.SetPdfToSign(userfileContent);
padesSigner.SetSigningCertificate(certWithKey);
padesSigner.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(Util.GetTrustArbitrator()));
padesSigner.ComputeSignature();
var signedPdf = padesSigner.GetPadesSignature();
```
### Criar uma assinatura CAdES
O CAdES assina qualquer arquivo binário com o `CadesSigner`; `GetSignature` retorna o pacote CMS (um arquivo `.p7s` nos exemplos). Para assinar no navegador, use `GenerateToSignBytes` e `SetPrecomputedSignature`; para finalizar depois, por exemplo num background worker, use `SetFinishSignature(false)` e o `CadesSignatureFinisher`. Veja [Assinaturas padrão CAdES](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades) e [Finalização assíncrona de assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades/finisher).
```csharp
var cadesSigner = new CadesSigner();
cadesSigner.SetDataToSign(userfileContent);
cadesSigner.SetSigningCertificate(certWithKey);
cadesSigner.SetPolicy(CadesPoliciesForGeneration.GetPkiBrazilAdrBasica());
cadesSigner.ComputeSignature();
var signatureContent = cadesSigner.GetSignature();
```
### Assinar um documento XML
O `FullXmlSigner` assina um documento XML inteiro (por padrão, a assinatura vira filha do elemento raiz); o `XmlElementSigner` com `SetToSignElementId` assina um único elemento, que precisa ter um atributo ID único. Entre as políticas estão `XmlPolicySpec.GetXadesBasic`, `BrazilXmlPolicySpec.GetAdrBasicaV22` e `BrazilXmlPolicySpec.GetNFePadraoNacional`; com o Web PKI, use `GenerateToSignHash` e `SetPrecomputedSignature`. Veja [Assinaturas padrão XML](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/xml).
```csharp
var signer = new FullXmlSigner();
signer.SetXml(xmlContent);
signer.SetSigningCertificate(certWithKey);
signer.SetPolicy(BrazilXmlPolicySpec.GetNFePadraoNacional());
signer.ComputeSignature();
var signedXml = signer.GetSignedXml();
```
### Abrir e validar uma assinatura
Abra um arquivo assinado e valide cada signatário segundo uma política: `PadesSignature.Open` e `ValidateSignature` para PDF, `CadesSignature.Open` para CAdES e `XmlSignatureLocator.GetSignatures` para XML. Cada validação retorna um `ValidationResults`. Veja [Assinaturas padrão CAdES](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades).
```csharp
var signature = PadesSignature.Open(userfileContent);
var policyMapper = PadesPoliciesForValidation.GetPadesBasic(Util.GetTrustArbitrator());
foreach (var signerInfo in signature.Signers) {
ValidationResults vr = signature.ValidateSignature(signerInfo, policyMapper);
Console.WriteLine(vr);
}
```
### Autenticar um usuário com certificado
`PKCertificateAuthentication.Start` gera um nonce, o navegador o assina com o certificado do usuário e `Complete` verifica a assinatura e valida o certificado. Os nonces ficam num `INonceStore`: o exemplo usa o `FileSystemNonceStore`, e o conector do Entity Framework oferece o `EntityFrameworkNonceStore`. Veja [Autenticação com certificado](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/auth).
```csharp
// GET: issue the nonce to be signed in the browser
var certAuth = new PKCertificateAuthentication(Util.GetNonceStore());
var nonce = certAuth.Start();
// POST: check the nonce signature and the certificate
PKCertificate certificate;
var vr = certAuth.Complete(model.Nonce, model.Certificate, model.Signature, Util.GetTrustArbitrator(), out certificate);
if (!vr.IsValid) {
// authentication failed: show vr.ToString()
}
```
### Gerar uma versão para impressão
Marque um PDF assinado com um código de verificação e um resumo dos signatários: `AlphaCode.Generate` cria o código e `PdfMarker.WriteMarks` grava as marcas; as assinaturas são removidas da cópia marcada. No exemplo, `allPagesMessage` cita os signatários de `PadesSignature.Open(pdfContent)` e mostra `AlphaCode.Format(verificationCode)`. Veja [Versão para impressão](https://docs.lacunasoftware.com/articles/pki-sdk/printer-friendly-version) e [Gerando códigos alfanuméricos](https://docs.lacunasoftware.com/articles/pki-sdk/alpha-codes).
```csharp
var verificationCode = AlphaCode.Generate(); // store it with the document
var pdfMarker = new PdfMarker();
pdfMarker.AddMark(new PdfMark() {
PageOption = PdfMarkPageOptions.AllPages,
Container = new PadesVisualRectangle() { Height = 2, Bottom = 0, Left = 1.5, Right = 3.5 },
Elements = new List() {
new PdfMarkText() { Texts = new List() { new PdfTextSection() { Style = PdfTextStyle.Normal, Text = allPagesMessage } } }
}
});
pdfMarker.ThrowIfSignedPdf = false;
var pfvContent = pdfMarker.WriteMarks(pdfContent);
```
## Erros
Os erros chegam como exceções, nos assinadores, e como `ValidationResults`, nas validações:
- Os assinadores lançam [`ValidationException`](https://docs.lacunasoftware.com/api/Lacuna.Pki/ValidationException) quando uma verificação falha, por exemplo um encoding de certificado ilegível ou um certificado expirado ou revogado; a propriedade `ValidationResults` informa o motivo, e os exemplos mostram `ex.ValidationResults.ToString()` ao usuário.
- As validações não lançam exceção: `PKCertificate.Validate`, `ValidateSignature` e `PKCertificateAuthentication.Complete` retornam um [`ValidationResults`](https://docs.lacunasoftware.com/api/Lacuna.Pki/ValidationResults) com `IsValid`, `Errors`, `Warnings` e `PassedChecks`; uma autenticação que falha é normal com um certificado expirado ou revogado.
- Para tratar uma verificação como alerta em vez de erro, defina o nível dela em `CertificateValidationOptions`, por exemplo `SetFailureLevel(ValidationItemTypes.CertificateRevocationStatusUnknown, ValidationFailureLevels.Warning)`.
- Com uma política que exige carimbo de tempo, como a AD-RT, o `ComputeSignature` lança [`TimestampException`](https://docs.lacunasoftware.com/api/Lacuna.Pki/TimestampException) se não conseguir o carimbo; a `TimestampRequestException`, que identifica erros na requisição do carimbo de tempo, deriva dela.
- O `PdfMarker` lança `SignedPdfToMarkException` para um PDF que tenha qualquer assinatura, a menos que `ThrowIfSignedPdf` seja `false`.
TODO(docs): nenhum artigo diz quando são lançadas `LicenseException`, `InvalidPdfException`, `PasswordProtectedPdfException` ou `PkiWebException`, por exemplo com uma licença ausente ou expirada; a [referência da API](https://docs.lacunasoftware.com/api/Lacuna.Pki) as lista sem descrição.
## Certificados e credenciais de teste
Use os [certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs): arquivos PKCS #12 com a senha **1234**, emitidos pela ICP de testes da Lacuna Software, que nunca deve ser aceita em ambiente de produção.
Com o PKI SDK, confie neles adicionando a raiz de teste da Lacuna como `TrustedRoots` a um `LinkedTrustArbitrator`, só em builds de debug, como faz o `Util.GetTrustArbitrator()` dos exemplos. Veja [PKI SDK](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#pki-sdk) na página de certificados de teste.
Políticas da ICP-Brasil como `CadesPoliciesForGeneration.GetPkiBrazilAdrBasica()` e `BrazilXmlPolicySpec.GetNFePadraoNacional()` confiam só nas raízes da ICP-Brasil, por isso, em builds de debug, os exemplos substituem o árbitro de confiança delas pelo `Util.GetTrustArbitrator()`: `ClearTrustArbitrators` e `AddTrustArbitrator` num `XmlPolicySpec`, `ClearTrustArbitrator` e `AddTrustArbitrator` num `CadesPolicySpec`.
Para obter uma licença de trial, entre em contato pelo site pki.lacunasoftware.com; o projeto de exemplos traz o próprio `LacunaPkiLicense.config`, copiado para o diretório de saída.
## Experimente
Demos online e os exemplos do PKI SDK para ASP.NET MVC e ASP.NET Core com Angular:
**Demonstrações e exemplos**
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Demonstração: [Sign a PDF with your cloud certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-with-cloud-certificate)
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Sign a PDF with PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureSdkController.cs)
- Sign multiple PDFs with PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/BatchPadesSignatureSdkController.cs)
- Sign a PDF with a server key using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesServerKeySdkController.cs)
- Sign a PDF with a cloud certificate (OAuth flow) using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesCloudOauthSdkController.cs)
- Sign a PDF with a cloud certificate (password flow) using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesCloudPwdSdkController.cs)
- Sign a file with CAdES using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesSignatureSdkController.cs)
- Sign multiple files with CAdES using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/BatchCadesSignatureSdkController.cs)
- Sign a file with CAdES and a server key using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesServerKeySdkController.cs)
- Merge CMS/CAdES signatures using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/MergeCadesSdkController.cs)
- Sign an XML document using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlSignatureSdkController.cs)
- Sign an XML document with a server key using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlServerKeySdkController.cs)
- Sign a Brazilian NF-e using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlNFeSignatureSdkController.cs)
- Sign a COD XML using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlCodSignatureSdkController.cs)
- Validate a signed PDF using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenPadesSignatureSdkController.cs)
- Validate a CMS/CAdES signature using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenCadesSignatureSdkController.cs)
- Validate a signed XML using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenXmlSignatureSdkController.cs)
- Make a printer-friendly version of a signed PDF using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyPadesSdkController.cs)
- Make a printer-friendly version of a CMS/CAdES signature using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyCadesSdkController.cs)
- Authenticate with a digital certificate using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/AuthenticationSdkController.cs)
- Sign a medical prescription (Receita Simples) using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/ReceitaSimplesSdkController.cs)
# [Lacuna PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk)
O PKI SDK é uma biblioteca para .NET Framework para realizar operações com certificados digitais (X.509).
> **Dica**
>
> O PKI SDK faz parte da nossa linha de soluções para certificação digital. Recomendamos que você leia o artigo
> [Certificação Digital](https://docs.lacunasoftware.com/articles/pki-guide) para ter certeza de que esse é o produto mais indicado para o seu caso.
Com o PKI SDK, é possível:
* Decodificação e validação de certificados de chave pública
* Decodificação, validação e emissão de certificados de atributo
* Criação e validação de assinaturas padrão CMS e CAdES
* Criação e validação de assinaturas padrão PAdES
* Criação e validação de assinaturas padrão XMLDSig e XAdES
Esta documentação contém informações conceituais sobre o uso da biblioteca, exemplos de código e o guia completo da API.
Para começar a usar o SDK, veja o tópico [Primeiros Passos](https://docs.lacunasoftware.com/articles/pki-sdk/get-started) ou acesse os [projetos de exemplo](https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/dotnet).
# [Primeiros Passos](https://docs.lacunasoftware.com/articles/pki-sdk/get-started)
Para começar a utilizar o Lacuna PKI SDK, você precisará seguir as seguintes instruções:
* [Acessando pacotes nuget](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/nuget)
* [Licenciamento](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/licensing)
Ao final desse processo, você deverá ter conseguido adicionar o pacote Lacuna PKI SDK ao seu projeto e deve ter configurado a licença de uso do SDK. Em seguida, veja como utilizar o SDK acessando os próximos tópicos como [Certificados](https://docs.lacunasoftware.com/articles/pki-sdk/certificates) e [Assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures).
# [Licenciamento](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/licensing)
Para habilitar o uso do Lacuna PKI SDK é necessário carregar a licença na inicialização da aplicação. A licença pode ser utilizada nos formatos
* Arquivo LacunaPKILicense.config
* String Base64
Se você ainda não possui uma licença, entre em contato através do site pki.lacunasoftware.com para obter uma licença de trial gratuitamente.
Existem 3 maneiras de se carregar a licença. A maneira mais recomendada depende se sua aplicação é uma aplicação web ou desktop:
* Para aplicações **web**, o método mais recomendado é colocar o [arquivo LacunaPKILicense.config no diretório da DLL](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/licensing#side-loaded-file)
* Para aplicações **desktop**, o método mais recomendado é setar a licença [Programaticamente](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/licensing#programmatically)
## Arquivo LacunaPKILicense.config no diretório da DLL
O SDK busca automaticamente o arquivo .config da licença junto à sua DLL (Lacuna.Pki.dll). Portanto, basta que o arquivo `LacunaPKILicense.config` seja copiado durante a compilação para o mesmo local da DLL.

Uma forma prática de fazer isso é incluir o arquivo `LacunaPKILicense.config` no projeto do seu site ou aplicação e especificar o **Build Action** do arquivo como **Content** e setar opção **Copy to Output Directory** para **Copy Always**.

Essa forma é a mais recomendada para aplicações web.
## Arquivo .config
Outra opção é incluir no arquivo `Web.config` ou `App.config` do seu site ou aplicação uma tag com o caminho para o arquivo .config da licença ou ainda uma tag com a licença em Base64, conforme exemplificado abaixo:
```xml
```
## Programaticamente
A licença pode ser carregada programaticamente fazendo uma chamada ao método @Lacuna.Pki.PkiConfig.LoadLicense(System.Byte[]) passando o valor decodificado da licença em Base64, preferencialmente no código de inicialização do seu site ou aplicação:
```csharp
// Licença em Base64
byte[] binLicense = Convert.FromBase64String("[given Base64 License]");
// Carregando Licença
PkiConfig.LoadLicense(binLicense);
// Uso habilitado
```
**Esta forma é a mais recomendada para aplicações desktop**, pois a licença fica embutida no código fonte da aplicação, dificultando seu vazamento para terceiros.
Também é possível utilizar o método @Lacuna.Pki.PkiConfig.LoadLicense(System.String) para passar o caminho para o arquivo .config da licença. Entretanto, esse método **não é recomendado para aplicações desktop** pois não tem a vantagem de "esconder" a licença.
```csharp
// Caminho para o arquivo LacunaPKILicense.config
string licensePath = "...";
// Carregando licença
PkiConfig.LoadLicense(licensePath);
// Uso habilitado
```
## Próximos passos
Tendo adicionado o pacote Lacuna PKI SDK ao seu projeto e configurado a licença de uso do mesmo, veja como utilizar o SDK acessando os próximos tópicos como [Certificados](https://docs.lacunasoftware.com/articles/pki-sdk/certificates) e [Assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures).
## Veja também
* Classe @Lacuna.Pki.PkiConfig
# [Acessando pacotes nuget](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/nuget)
O SDK é distribuído através do Nuget Package Manager. Para adicionar o pacote do SDK ao seu projeto, abra o Nuget Package Manager (clique com o botão direito no seu projeto e clique em **Manage Nuget Packages...**).

Siga então os passos abaixo:
0. Clique no item **Online** na coluna esquerda
0. Digite **Lacuna** no campo de pesquisa, no canto superior direito
0. Aparecerão os pacotes relacionados ao SDK. Clique no pacote **Lacuna PKI SDK**
0. Verifique o Id do pacote, que deve corresponder a **Lacuna.Pki**
0. Clique então em **Install**

Os demais pacotes são opcionais e habilitam integração com provedores como Microsoft Azure e Amazon Web Services, bem como integração com bibliotecas como Entity Framework, NLog e iTextSharp. Para saber mais sobre os pacotes opcionais, veja [Pacotes nuget opcionais](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages).
O próximo passo é configurar a licença de uso SDK (veja o tópico [Licenciamento](https://docs.lacunasoftware.com/articles/pki-sdk/get-started/licensing)).
# [Certificados](https://docs.lacunasoftware.com/articles/pki-sdk/certificates)
A classe @Lacuna.Pki.PKCertificate provê visualização e manipulação de informações de certificados X509. A classe
suporta o carregamento de certificados com encoding BER, DER, Base64 ou PEM.
```cs
// Para certificados com encoded bytes em BER, DER, Base64 ou PEM
PKCertificate PKCertificate.Decode(byte[] certContent);
// Para certificados com encoding Base64 ou PEM
PKCertificate PKCertificate.Decode(string certContent);
```
Para mais exemplos de como carregar um certificado e iniciar o uso em nosso SDK, verifique o tópico seguinte:
[Carregando certificados](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/decoding)
## Veja também
* @Lacuna.Pki.PKCertificate
# [Certificados de Atributo](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/attribute-certs)
Um certificado de atributo é uma estrutura similar a um certificado de chave pública, com a diferença que o certificado
de atributo não contém chave. Um certificado de atributo pode conter atributos que especificam participação em grupos,
papéis, autorizações de segurança ou outras informações de autorização associadas ao titular (*holder*) do certificado.
O PKI SDK é capaz de decodificar e validar certificados de atributo através da classe @Lacuna.Pki.AttributeCertificate,
bem como emitir certificados de atributo, com a classe @Lacuna.Pki.AttributeCertificateGenerator.
## Hierarquia de classes
Como um certificado de atributo é similar a um certificado de chave pública, as classes que representam tais estruturas
no SDK estão relacionadas, possuindo um ancestral comum:

> **Observação**
>
> O diagrama acima está incompleto, mostrando apenas algumas das propriedades e métodos de cada classe
Observe que algumas propriedades presentes na classe @Lacuna.Pki.PKCertificate são, na verdade, da classe
@Lacuna.Pki.Certificate, ancestral comum com a classe @Lacuna.Pki.AttributeCertificate. Portanto, muitas das operações
que são possíveis com certificados de chave pública funcionam da mesma forma para certificados de atributo,
especialmente a propriedade @Lacuna.Pki.Certificate.Issuer e o método
@Lacuna.Pki.Certificate.Validate(Lacuna.Pki.ITrustArbitrator).
As informações do titular, no entando, não ficam na classe ancestral, mas sim nas classes filhas. Isso porque, no caso
de certificados de chave pública, o titular (*subject*) é um @Lacuna.Pki.Name, enquanto que em certificados de atributo
o titular (*holder*) pode ser um @Lacuna.Pki.GeneralNames ou mesmo um certificado de chave pública. Esses dois valores
possíveis são acessíveis pelas propriedades @Lacuna.Pki.AttributeCertificate.HolderName e
@Lacuna.Pki.AttributeCertificate.HolderPKCertificate. A propriedade @Lacuna.Pki.Certificate.SubjectDisplayName é um
mero auxílio visual para tratar certificados de chave pública e de atributo como uma entidade comum, mas esse é
justamente um aspecto no qual eles diferem.
## Decodificando e validando certificados de atributo
Por causa da hierarquia comum com a classe @Lacuna.Pki.PKCertificate, as operações de decodificação e validação de
certificados de atributo são muito similares às mesmas operações com certificados de chave pública:
```cs
byte[] acContent = ...;
AttributeCertificate ac = AttributeCertificate.Decode(acContent);
ValidationResults vr = ac.Validate(TrustArbitrators.Windows);
if (!vr.IsValid) {
// ...
}
```
## Decodificando atributos
Uma dificuldade adicional no tratamento de certificados de atributo é decodificar os atributos contidos no certificado,
cuja codificação muitas vezes é proprietária. A classe @Lacuna.Pki.Asn1.Asn1Util oferece métodos para codificar e
decodificar objetos ASN.1, que é a codificação geralmente utilizada para os atributos.
Para utilizar os métodos de codificação e decodificação, é necessário fazer classes que representem as estruturas que
se deseja codificar e decodificar e denotar com atributos a sintaxe ASN.1 das estruturas, como abaixo:
```cs
// MembershipIdAttribute ::= SEQUENCE {
// memberId INTEGER,
// memberNickname UTF8String
// }
[Asn1Sequence]
public class MembershipIdAttribute {
[Asn1SequenceElement(0, Asn1PrimitiveTypes.Integer)]
public int MemberId { get; set; }
[Asn1SequenceElement(1, Asn1PrimitiveTypes.UTF8String)]
public string MemberNickname { get; set; }
}
public class Program {
public static void Main(string[] args) {
MembershipIdAttribute memberAttribute = new MembershipIdAttribute() {
MemberId = 12345,
MemberNickname = "Mr. Bison"
};
// Encode in DER
byte[] encodedAttribute = Asn1Util.DerEncode(memberAttribute);
// Decode from DER/BER
MembershipIdAttribute decodedAttribute = Asn1Util.DecodeAs(encodedAttribute);
}
}
```
Uma vez tendo as classes representando os atributos e decoradas com as indicações de sintaxe ASN.1, é possível obter os
atributos da seguinte forma:
```cs
byte[] acContent = ...;
// Decodificamos o certificado
AttributeCertificate ac = AttributeCertificate.Decode(acContent);
// Buscamos um atributo pelo seu OID
X509Attribute attributeObj = ac.Attributes["1.2.3.4"];
// Podem haver múltiplos valores para um mesmo OID, digamos que nesse caso estamos interessados apenas no primeiro valor
byte[] encodedAttribute = attributeObj.EncodedValues[0];
// Decodificamos o atributo
MembershipIdAttribute memberAttribute = Asn1Util.DecodeAs(encodedAttribute);
```
O método @Lacuna.Pki.X509Attributes.GetAndDecodeValueAs``1(System.String,System.Boolean) permite buscar um atributo e
decodificar seu primeiro valor diretamente:
```cs
byte[] acContent = ...;
// Decodificamos o certificado
AttributeCertificate ac = AttributeCertificate.Decode(acContent);
// Buscamos e decodificamos o primeiro valor de um atributo pelo seu OID
MembershipIdAttribute memberAttribute = ac.Attributes.GetAndDecodeValueAs("1.2.3.4");
```
## Veja também
* @Lacuna.Pki.Certificate
* @Lacuna.Pki.AttributeCertificate
* @Lacuna.Pki.AttributeCertificateGenerator
* @Lacuna.Pki.Asn1.Asn1Util
# [Autenticação com certificado](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/auth)
Uma das principais utilidades de certificados digitais de chave pública é autenticar remotamente usuários. Para tanto,
é preciso (1) verificar a validade do certificado do usuário no momento da autenticação e (2) verificar que o usuário
de fato possui a chave privada associada à chave pública do certificado (toma-se isso como prova de que o usuário é o
titular do certificado). Para auxiliar nesse processo, o SDK disponibiliza a classe
@Lacuna.Pki.PKCertificateAuthentication.
O processo de verificação se o usuário possui a chave privada associada à chave pública do certificado envolve a geração
de um *nonce* criptográfico, um número gerado aleatoriamente que nunca é utilizado duas vezes, assinatura remota desse
*nonce* pelo usuário utilizando a chave privada associada ao certificado e subsequente verificação da assinatura com a
chave pública do certificado.
## Nonce stores
Para proteger tal protocolo de ataques de replay, é necessário garantir que um usuário não consiga autenticar-se
apresentando a assinatura do mesmo *nonce* duas vezes. Em outras palavras, é preciso guardar o estado dos *nonces*
gerados em algum tipo de storage externo. Chamaremos um storage externo capaz de armazenar *nonces* criptográficos de
*nonce store*. A interface @Lacuna.Pki.Stores.INonceStore define a interação entre o SDK e tais *nonce stores*:
```cs
public interface INonceStore {
void Store(byte[] nonce);
bool CheckAndRemove(byte[] nonce);
}
```
O funcionamento de um *nonce store* é muito simples: *nonces* passados ao método
@Lacuna.Pki.Stores.INonceStore.Store(System.Byte[]) devem ser armazenados para futura consulta pelo método
@Lacuna.Pki.Stores.INonceStore.CheckAndRemove(System.Byte[]), que retorna se o *nonce* foi anteriormente armazenado ou
não e apaga-o se sim, fazendo com que uma subsequente consulta com o mesmo nonce retorne `false`.
O pacote opcional [Entity Framework Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/ef-connector) oferece a implementação
recomendada dessa interface, a classe @Lacuna.Pki.EntityFrameworkConnector.EntityFrameworkNonceStore, que armazena os
*nonces* no banco de dados. Entretanto, você pode criar a sua implementação da interface persistindo o estado dos
*nonces* onde quiser. Veja a [documentação da interface para maiores detalhes](https://docs.lacunasoftware.com/api/Lacuna.Pki.Stores/INonceStore) ou
até mesmo a
[implementação da classe EntityFrameworkNonceStore](https://bitbucket.org/Lacunas/pkientityframeworkconnector/src/922cba056a43805e208cfcf7834558986d04c14d/PkiEntityFrameworkConnector/EntityFrameworkNonceStore.cs?at=master).
## Implementando a autenticação
Tendo uma instância de uma classe que implemente a interface @Lacuna.Pki.Stores.INonceStore, pode-se então instanciar
a classe @Lacuna.Pki.PKCertificateAuthentication:
```cs
INonceStore nonceStore = ...;
var certAuth = new PKCertificateAuthentication(nonceStore);
```
Utiliza-se a classe nos dois momentos da autenticação: na geração do *nonce* e na subsequente verificação da assinatura
do *nonce*.
```cs
public class AuthenticationController : ApiController {
public IHttpActionResult Get() {
INonceStore nonceStore = ...;
var certAuth = new PKCertificateAuthentication(nonceStore);
// Geramos um nonce chamando o método Start()
var nonce = certAuth.Start();
var response = new AuthenticationGetResponse() {
Nonce = nonce
};
return Ok(response);
}
public IHttpActionResult Post(AuthenticationPostRequest request) {
ValidationResults vr;
PKCertificate certificate;
INonceStore nonceStore = ...;
var certAuth = new PKCertificateAuthentication(nonceStore);
// Verificamos a assinatura do nonce e a validade do certificado chamando o método Complete()
vr = certAuth.Complete(request.Nonce, request.Certificate, request.Signature, TrustArbitrators.Windows, out certificate);
// Check authentication result
if (!vr.IsValid) {
// This failure is perfectly normal, it happens everytime a user tries to sign in
// with an expired or revoked certificate, for instance.
return Ok(new AuthenticationPostResponse() {
Success = false,
Message = "Authentication failed",
ValidationResults = vr.ToString()
});
}
// ----------------------------------------------------------------------------------------
// At this point, you have assurance that the certificate is valid according to the
// TrustArbitrator you selected above and that the user is indeed the certificate's
// subject. Now, you'd typically query your database for a user that matches one of the
// certificate's fields, such as cert.EmailAddress or cert.PkiBrazil.CPF and set the user
// ID on the auth cookie. For demonstration purposes, we'll set the email address directly
// on the cookie as if it were the user ID.
// ----------------------------------------------------------------------------------------
FormsAuthentication.SetAuthCookie(certificate.EmailAddress, false);
// Inform the page of the success
return Ok(new AuthenticationPostResponse() {
Success = true
});
}
}
```
Para um exemplo completo incluindo a parte client-side, veja o projeto
[PkiSuiteSamples](https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/dotnet) no GitHub.
**Demonstrações e exemplos**
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Authenticate with a digital certificate using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/AuthenticationSdkController.cs)
# [Certificados com chave privada associada](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/certs-with-key)
A classe @Lacuna.Pki.PKCertificateWithKey detém informação de certificados que possuem chave privada associada e
permite operações seguras com esta chave.
As diferentes maneiras de se carregar certificados com chave privada são apresentadas nas seções seguintes.
## Carregando certificados pessoais do Windows Store
```cs
// Carregando certificados pessoais do Windows Store
var store = WindowsCertificateStore.LoadPersonalCurrentUser();
// Selecionando os certificados com chave privada associada
var certsWithKey = store.GetCertificatesWithKey();
// Exibindo informações dos certificados
certsWithKey.ForEach(c => Console.WriteLine(c.Certificate));
```
A classe `WindowsCertificateStore` além de carregar certificados pessoais do usuário, também permite
carregar certificados da máquina ou de um `StoreName` e `StoreLocation` passado.
Para mais informações de opções para carregar certificados do Windows Store, verifique os métodos de "Load" no guia
da API: @Lacuna.Pki.Stores.WindowsCertificateStore
## Carregando certificado de um PKCS#12
```cs
// PKCS#12 bytes e password
byte[] pkcs12Content = ...
string password = ...
// Carregando um PKCS#12
var p12 = Pkcs12CertificateStore.Load(pkcs12Content, password);
// Selecionando os certificados com chave privada associada
var certsWithKey = p12.GetCertificatesWithKey();
// Exibindo informações dos certificados
certsWithKey.ForEach(c => Console.WriteLine(c.Certificate));
```
## Operações com chave
A classe @Lacuna.Pki.PKCertificateWithKey permite realizar as seguintes operações com a chave privada do certificado:
* Realização de algoritmo de assinatura (métodos @Lacuna.Pki.PKCertificateWithKey.SignData(Lacuna.Pki.SignatureAlgorithm,System.Byte[]) e `Lacuna.Pki.PKCertificateWithKey.SignHash(Lacuna.Pki.SignatureAlgorithm,System.Byte[])`)
* Verificação de algoritmo de assinatura (métodos @Lacuna.Pki.PKCertificateWithKey.VerifyData(Lacuna.Pki.SignatureAlgorithm,System.Byte[],System.Byte[]) e `Lacuna.Pki.PKCertificateWithKey.VerifyHash(Lacuna.Pki.SignatureAlgorithm,System.Byte[],System.Byte[])`)
Seu uso é exemplificado a seguir.
### Assinatura de dados
```cs
PKCertificateWithKey certWithKey = ...
// Definindo algoritmo de digest
var digestAlgorithm = DigestAlgorithm.SHA256;
// Definindo bytes a serem assinados
var toSign = System.Text.Encoding.ASCII.GetBytes("hello pki");
// Assinando com certificado
var signature = certWithKey.SignData(digestAlgorithm, toSign);
// Verificando assinatura
Console.WriteLine(certWithKey.VerifyData(digestAlgorithm, toSign, signature));
```
### Assinatura de hash
```cs
PKCertificateWithKey certWithKey = ...
// Definindo algoritmo de digest
var digestAlgorithm = DigestAlgorithm.SHA256;
// Computando hash a ser assinado
var toSignHash = digestAlgorithm.ComputeHash(System.Text.Encoding.ASCII.GetBytes("hello pki"));
// Assinando com certificado
var signature = certWithKey.SignHash(digestAlgorithm, toSignHash);
// Verificando assinatura
Console.WriteLine(certWithKey.VerifyHash(digestAlgorithm, toSignHash, signature));
```
## Veja também
* @Lacuna.Pki.PKCertificateWithKey
* @Lacuna.Pki.Stores.WindowsCertificateStore
* @Lacuna.Pki.Stores.Pkcs12CertificateStore
# [Construção assistida de cadeias](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/chain-building)
Ao carregar um certificado utilizando os métodos @Lacuna.Pki.PKCertificate.Decode(System.Byte[]) ou
@Lacuna.Pki.PKCertificate.Decode(System.String), o PKI SDK automaticamente tentará montar a cadeia do certificado
através de recursos online como AuthorityInformationAccess, OCSP requests, Certificate Store de ACs interno da SDK,
entre outros. É fundamental que se tenha uma cadeia completa para a validação de um certificado, pois esta validação
também é realizada em seus emissores de forma recursiva. Os certificados das ACs encontrados online são guardados
no Certificate Store interno do SDK, oferecendo ótimo desempenho para próximas operações.
Ao utilizar certificados que possuem informação online de emissores ou certificados que seguem padrões conhecidos,
dificilmente haverá necessidade de auxiliar o processo de completar cadia de certificados.
Caso o usuário deseje utilizar certificados com características distintas ou padrões privados que impossibilitem
completar as cadeias de maneira automática, o SDK oferece algumas soluções para assistir esta ação, detalhadas
nas seções seguintes.
## Incluindo ACs no Windows Store
Caso o conjunto de ACs intermediárias aos certificados tenha uma cardinalidade limitada, é possivel incluí-las no
diretório **Autoridades de Certificação Intermediárias > Certificados** no Certificate Manager do usuário corrente
ou Computador Local. O SDK utilizará esse store local como fonte para completar a cadeia dos certificados.
Para mais detalhes de como importar certificados no Microsoft Management Console (MMC) veja o tópico **Como configurar
certificados intermediários** no artigo da Microsoft
[Como configurar certificados intermediários em um computador que está executando o IIS para autenticação de servidor](https://support.microsoft.com/pt-br/help/954755)
## Parâmetro ICertificateStore
A biblioteca oferece o uso de um store de certificados em memória (`Lacuna.Pki.Stores.MemoryCertificateStore`) e
também a possibilidade do usuário criar sua própria implementação da interface @Lacuna.Pki.Stores.ICertificateStore caso
deseje utilizar uma estrutura mais complexa e comunicável com outros sistemas como banco de dados, cache, entre outros.
O parâmetro pode ser passado tanto no momento de carga do certificado (métodos
`Lacuna.Pki.PKCertificate.Decode(System.Byte[],Lacuna.Pki.Stores.ICertificateStore)`
ou
`Lacuna.Pki.PKCertificate.Decode(System.String,Lacuna.Pki.Stores.ICertificateStore)`)
quanto na propriedade @Lacuna.Pki.CertificateValidationOptions.CertificateStore das opções de validação
(`Lacuna.Pki.CertificateValidationOptions`).
Exemplo de uso na carga:
```cs
// Bytes do certificado a ser carregado
byte[] certContent;
// Lista de certificados ou ACs privadas
List certList;
// Certificate store
var certStore = new MemoryCertificateStore(certList);
// Carregando certificado e completando cadeia
var cert = PKCertificate.Decode(certContent, certStore);
```
Exemplo de uso nas opções de validação:
```cs
// Certificado carregado
var cert = ...
// Lista de certificados ou ACs privadas
List certList;
// Certificate store
var certStore = new MemoryCertificateStore(certList);
// Opções de validação com raízes do Windows confiáveis e certStore do usuário
var options = new CertificateValidationOptions(TrustArbitrors.Windows);
options.CertificateStore = certStore;
// A cadeia será completada durante a validação
cert.Validate(options);
```
## Veja também
* @Lacuna.Pki.Stores.ICertificateStore
* @Lacuna.Pki.Stores.MemoryCertificateStore
* @Lacuna.Pki.PKCertificateWithKey
* [Como configurar certificados intermediários em um computador que está executando o IIS para autenticação de servidor](https://support.microsoft.com/pt-br/help/954755)
# [Carregando certificados](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/decoding)
Utilizando os métodos @Lacuna.Pki.PKCertificate.Decode(System.Byte[]) ou @Lacuna.Pki.PKCertificate.Decode(System.String)
é possível carregar certificados com encoding BER, DER, Base64 ou PEM.
```cs
// Para certificados com encoded bytes em BER, DER, Base64 ou PEM
PKCertificate PKCertificate.Decode(byte[] certContent);
// Para certificados com encoding Base64 ou PEM
PKCertificate PKCertificate.Decode(string certContent);
```
## Carregamento básico
Para este exemplo, usaremos um certificado de teste, emitido pela AC de teste Lacuna CA.
```cs
// Certificado com encoding PEM
var pem = "-----BEGIN CERTIFICATE-----\nMIIFLTCCBBegAwIBAgIQAPlQfZfK+GbabULPDzWHmTALBgkqhkiG9w0BAQswVTELMAkGA1UEBhMCQlIxEzARBgNVBAoMCklDUC1CcmFzaWwxHTAbBgNVBAsMFExhY3VuYSBTb2Z0d2FyZSAtIExTMRIwEAYDVQQDDAlMYWN1bmEgQ0EwHhcNMTUwMTIwMTAyMzU5WhcNMTkwMTIwMTAyMzU1WjBtMQswCQYDVQQGEwJCUjETMBEGA1UECgwKSUNQLUJyYXNpbDEuMCwGA1UECwwlQXV0aGVudGljYXRlZCBieSBMYWN1bmEgU29mdHdhcmUgLSBMUzEZMBcGA1UEAwwQUGllcnJlIGRlIEZlcm1hdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALzmguTacZ4YiFpzUvE9xQEFB2bs8FXdXK8DCEDIq6mTudxFQzicCvF1rn5tgJZVDl4ANjKGDnQFUUhW50gf9xCxcytYkf7jWeabiGrHYweQggoTbguc8mY/OIl1W+0GAoVtpFrBs27zHcE4kPu9DJwJjKNHp00SuRkx35WezRRas940vg15eZKFneyw0VaacJelVdtSwto5HrsoFgQIEQhh/33FHhfVt9XnZ1UiZVZPZcOETJ8ebJpjtKL83yy4QKFc235dp0iudAdCVIY4oAlwVjCn6U9dIZk7qR0H1OYE1f2z8lTtiWx/jg/j+bBpMayVz7HEFqJe/mFBimeY5ZECAwEAAaOCAeMwggHfMAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAU0qjYpb7yDHVFWfnN4BUYSk7c2/cwDgYDVR0PAQH/BAQDAgXgMIGmBgNVHREEgZ4wgZugPQYFYEwBAwGgNAQyMDEwMTE5NzA0NzM2MzM2MTg4NjAwMDAwMDAwMDAwMDAwMDAwMDQ5Mzg4ODI3U1NQREagFwYFYEwBAwagDgQMODg1NTc3NTM0NjUyoCgGBWBMAQMFoB8EHTkxMjkzOTIxOTc3NTc3NzY2NjZCcmFzaWxpYURGgRd0ZXN0QGxhY3VuYXNvZnR3YXJlLmNvbTBQBgNVHSAESTBHMEUGBmBMAQIBADA7MDkGCCsGAQUFBwIBFi1odHRwOi8vY2F0ZXN0LmxhY3VuYXNvZnR3YXJlLmNvbS9kcGNhY3JmYi5wZGYwOQYDVR0fBDIwMDAuoCygKoYoaHR0cDovL2NhdGVzdC5sYWN1bmFzb2Z0d2FyZS5jb20vY3Jscy9jYTAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwTAYIKwYBBQUHAQEEQDA+MDwGCCsGAQUFBzAChjBodHRwOi8vY2F0ZXN0LmxhY3VuYXNvZnR3YXJlLmNvbS9jZXJ0aWZpY2F0ZXMvY2EwCwYJKoZIhvcNAQELA4IBAQAAyc4Ylt4Fa2n8DdfjFLTAt4Y78uR3Fo+on8yE2fUG/B/CNLw0TEuSKpvCra1igNjU4VmqRsPAGJiKGaOZe0XeuySVvd16z6J92GLI/02qKvPBuC2qaBF9eu+Hhd4G0G/gnIKFEr/LMoOkBrfU9CL/9jm3/MT2s9F8uF8OBjP0TDaMJkc3yNS9RYEhAM1EdfXUStEv4Zs+Yy+CkkL1vCha1+sH8zNdkYbNMxdGYQTrcG8I1TTh9NLxitTAM7j5JrPycp3rUvu/rq94VnBN9CzN62TWvkaFpQuZSg6FKrsjyGWbpUN96Y21p7QTUAliq226eGBk5BZD8+7ZuZGmG07J\n-----END CERTIFICATE-----";
// Carregando certificado
var cert = PKCertificate.Decode(pem);
// cert.GetCertificateChain()
// Exibindo informações básicas
Console.WriteLine(cert);
/* Output esperado:
Titular: Pierre de Fermat
Expira em: 20/01/2019 10:23:55 +00:00
Emissor: Lacuna CA
*/
```
É possível notar no output que além do certificado de teste (Pierre de Fermat) há somente o nome da AC que
emitiu o certificado de teste, não mostra as informações do certicado emissor. Isso ocorre pois a cadeia de
certificado é carregada em *lazy load*, ou seja, só será carregada no momento em que for realmente necessária.
O carregamento é feito dessa maneira, pelo fato da existência de certificados que não seguem os padrões já
estabelecidos para tornar possível a construção da cadeia de forma automática.
No entanto, para carregar a cadeia de certificados no momento da codificação, é necessário informar a Certificate Store
para o método Decode(), de forma a auxiliar o SDK a construir a cadeia de certificados. Para melhor compreensão desta
funcionalidade, verifique a seção a seguir: [Cadeia do certificate](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/decoding#chain)
## Cadeia do certificado
Ao carregar um certificado utilizando os métodos `Lacuna.Pki.PKCertificate.Decode(System.Byte[],Lacuna.Pki.Stores.ICertificateStore)` ou
`Lacuna.Pki.PKCertificate.Decode(System.String,Lacuna.Pki.Stores.ICertificateStore)`, o SDK automaticamente tentará montar a cadeia do
certificado através de recursos online como AuthorityInformationAccess, OCSP requests, Certificate Store de ACs interno
do SDK ou Certificate Store passado como parâmetro da função. Os certificados das ACs encontrados online são guardados no
Certificate Store interno do SDK, oferecendo desempenho otimizado para próximas operações.
A construção da cadeia é fundamental para o processo de validação de um certificado, pois esta validação também é
realizada em seus emissores de forma recursiva. O Lacuna PKI SDK apresenta boa aderência a diferentes padrões de PKI,
ao contrário de outras SDKs que não oferecem este recurso e requerem que ACs intermediárias sejam incluídas
manualmente.
Ainda que utilize certificados com características que impossibilitem completar a cadeia, o Lacuna PKI SDK oferece
soluções para auxiliar esta ação.
Para mais informações sobre construção assistida de cadeias, verifique o tópico:
[Construção assistida de cadeias](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/chain-building)
## Veja também
* @Lacuna.Pki.PKCertificate
* [Construção assistida de cadeias](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/chain-building)
# [Validando certificados](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/validation)
Para realizar a validação de um certificado, é preciso passar um @Lacuna.Pki.ITrustArbitrator
O `ITrustArbitrator` define um modo de arbitrar se a raíz da certificação é confiável. O SDK, através da classe estática
@Lacuna.Pki.TrustArbitrators, possui implementações de árbitros para validar raízes confiáveis do Windows, do padrão
ICP-Brasil e da PKI-Itália.
## Validando certificado
No código a seguir iremos, como exemplo, validar um certificado ICP-Brasil de maneira padrão.
```cs
// Carregando um certificado ICP-Brasil
PKCertificate cert = ...
// Validando com árbitro ICP-Brasil
var vr = cert.Validate(TrustArbitrators.PkiBrazil);
if (vr.isValid) {
// Certificado é válido
} else {
// Certificado é inválido
}
// Exibe detalhes da validação
Console.WriteLine(vr);
```
Exemplo anterior validando com árbitro do Windows:
```cs
// Validando com árbitro raízes confiáveis do Windows
var vr = cert.Validate(TrustArbitrators.Windows);
```
## Validando certificado com árbitros combinados
No código a seguir iremos realizar uma validação padrão de certificado com um conjunto de árbitros de confiança
contendo ICP-Brasil, Windows e raízes privadas de teste.
Deste modo, se o certificado for válido e pertencer a alguma das cadeias de certificação (ICP-Brasil, raízes do
Windows ou raízes privadas), a validação terá sucesso.
```cs
// Carregando um certificado
PKCertificate cert = ...
// Criando conjunto de árbitros
var arbitrator = new LinkedTrustArbitrator();
arbitrator.Add(TrustArbitrators.PkiBrazil); // Adiciona árbitro ICP-Brasil
arbitrator.Add(TrustArbitrators.Windows); // Adiciona árbitro Windows
arbitrator.Add(new TrustedRoots(rootTest1, rootTest2)); // Adiciona árbitro com raízes privadas de teste
// Validando
var vr = cert.Validate(arbitrator);
```
Também é possível criar o conjunto de árbitros através do construtor
`Lacuna.Pki.LinkedTrustArbitrator.%23ctor(Lacuna.Pki.ITrustArbitrator[])` ou também utilizando o método de extensão
@Lacuna.Pki.LinkedTrustArbitratorExtension.GetLinkedWith(Lacuna.Pki.ITrustArbitrator,Lacuna.Pki.ITrustArbitrator[])
conforme demonstrado no código abaixo:
```cs
// Outra maneira de criar o conjunto de árbitros
// através do construtor
var arbitrator = new LinkedTrustArbitrator(TrustArbitrators.PkiBrazil, TrustArbitrators.Windows, new TrustedRoots(rootTest1, rootTest2));
// Outra maneira de criar o conjunto de árbitros
// atrvés do método de extensão GetLinkedWith
var arbitrator = TrustArbitrators.PkiBrazil.GetLinkedWith(TrustArbitrators.Windows, new TrustedRoots(rootTest1, rootTest2));
```
## Alterando opções de validação
Caso seja necessário fazer alterações nas opções ou modo de validação do certificado, é possível instanciar a classe
@Lacuna.Pki.CertificateValidationOptions com um árbitro e então definir as propriedades da validação.
No código a seguir iremos, como exemplo, validar um certificado sem que seja validado a assinatura do seu emissor e em
caso de estado de revogação desconhecido, seja atribuído Warning ao invés de Error nos resultados da validação.
```cs
// Carregando um certificado
PKCertificate cert = ...
// Criando opções de validação com árbitro raízes confiáveis do Windows
var options = new CertificateValidationOptions(TrustArbitrators.Windows);
options.ValidateIssuerSignature = false;
options.SetFailureLevel(ValidationItemTypes.CertificateRevocationStatusUnknown, ValidationFailureLevels.Warning);
// Validando com opções alteradas
var vr = cert.Validate(options);
// Exibe detalhes da validação
Console.WriteLine(vr);
```
Para mais informações dos métodos e propriedades das opções de validação de certificado verifique o guia da API:
@Lacuna.Pki.CertificateValidationOptions
## Veja também
* @Lacuna.Pki.ITrustArbitrator
* @Lacuna.Pki.TrustArbitrators
* @Lacuna.Pki.LinkedTrustArbitrator
* @Lacuna.Pki.CertificateValidationOptions
# [Assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures)
O PKI SDK permite a customização e criação de assinaturas de PDF no padrão PAdES
([ETSI TS 102 778](http://www.etsi.org/deliver/etsi_TS/102700_102799/10277801/01.01.01_60/ts_10277801v010101p.pdf)),
assinaturas PKCS#7/CMS no padrão CAdES
([ETSI TS 101 733](http://www.etsi.org/deliver/etsi_ts/101700_101799/101733/02.02.01_60/ts_101733v020201p.pdf))
e assinaturas [XmlDSig](https://www.w3.org/TR/2002/REC-xmldsig-core-20020212/) simples e também conforme padrão XAdES
([ETSI TS 101 903](http://www.etsi.org/deliver/etsi_ts/101900_101999/101903/01.04.01_60/ts_101903v010401p.pdf)).
No exemplo abaixo faremos uma assinatura CAdES básica com o objetivo de apresentar e exercitar os conceitos e
propriedades desse assinador:
```cs
// Instanciamos o objeto signer que é responsável por criar a assinatura
var signer = new CadesSigner();
// Setamos os principais parâmetros
signer.SetSigningCertificate(signingCert); // certificado do signatário com chave privada associada
signer.SetDataToSign(toSign); // bytes ou stream do documento a ser assinado
signer.SetPolicy(policy); // política de assinatura
signer.ComputeSignature(); // cria atributos e colhe assinatura do signatário
var cadesSig = signer.GetSignature(); // monta o pacote de assinatura e retorna os bytes com encoding DER
```
No exemplo abaixo faremos uma assinatura PAdES básica com o objetivo de apresentar e exercitar os conceitos e
propriedades desse assinador:
```cs
// Instanciamos o objeto signer que é responsável por criar a assinatura
var signer = new PadesSigner();
// Setamos os principais parâmetros
signer.SetSigningCertificate(signingCert); // certificado do signatário com chave privada associada
signer.SetPdfToSign(pdfBytes); // bytes do PDF a ser assinado
signer.SetPolicy(policy); // política de assinatura
signer.ComputeSignature(); // cria colhe assinatura do signatário
var signedPdf = signer.GetPadesSignature(); // retorna os bytes do PDF assinado
```
## Veja também
* [Assinaturas padrão CAdES](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades)
* [Assinaturas padrão PAdES](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades)
* [Assinaturas padrão XML](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/xml)
* [Assinatura com chave remota (assinatura no browser)](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/web-remote)
* [Políticas de assinatura](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies)
# [Assinaturas padrão CAdES](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades)
O padrão CAdES permite que se faça uma assinatura de qualquer arquivo binário. A classe @Lacuna.Pki.Cades.CadesSigner é
responsável pela criação, customização e montagem do pacote de assinatura CMS.
## Exemplo de assinatura básica
O código abaixo exemplifica uma assinatura CAdES básica com o objetivo de apresentar e exercitar os conceitos e
propriedades desse assinador.
```cs
// Instanciamos o objeto signer que é responsável por criar a assinatura
var signer = new CadesSigner();
signer.SetSigningCertificate(signingCert); // certificado do signatário com chave privada associada
signer.SetDataToSign(toSign); // bytes ou stream do documento a ser assinado
signer.SetPolicy(CadesPolicySpec.GetCadesBes()); // política de assinatura CAdES-BES com raízes do Windows como TrustArbitrator
signer.ComputeSignature(); // cria atributos e colhe assinatura do signatário
var cadesSig = signer.GetSignature(); // monta o pacote de assinatura e retorna os bytes com encoding BER
```
## Exemplo de assinatura avançada
Abaixo exemplificamos uma assinatura CAdES com política de assinatura ICP-Brasil AD-RT (com carimbo de tempo) e em seguida
detalhamos cada método Set utilizado no assinador.
```cs
PKCertificateWithKey signingCert = ...
byte[] toSign = ...
// Política de assinatura ICP-Brasil AD-RT
var policy = BrazilCadesPolicySpec.GetAdrTempoV21();
// Criando requester de carimbo de tempo através da uri da carimbadora tsUri
var tsRequester = new TimestampRequester(tsUri);
// Instanciamos o objeto signer que é responsável por criar a assinatura
var signer = new CadesSigner();
signer.SetSigningCertificate(signingCert); // certificado do signatário com chave privada associada
signer.SetDataToSign(toSign); // bytes ou stream dos dados a serem assinados
signer.SetPolicy(policy); // política de assinatura
signer.SetTimestampRequester(tsRequester); // requester do carimbo de tempo
signer.ComputeSignature(); // cria atributos e colhe assinatura do signatário
var cadesSig = signer.GetSignature(); // monta o pacote de assinatura e retorna os bytes com encoding BER
```
* @Lacuna.Pki.Cades.CadesSigner.SetSigningCertificate(Lacuna.Pki.PKCertificateWithKey) -
Usado para definir o certificado do signatário na assinatura. No exemplo é utilizado um certificado com chave privada
associada. Para mais informações de como carregar um certificado com chave privada associada veja o artigo
[Certificados com chave privada associada](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/certs-with-key).
* @Lacuna.Pki.Cades.CadesSigner.SetDataToSign(System.Byte[]) -
Usado para definir os dados a serem assinados. O padrão CAdES permite que seja assinado qualquer tipo binário de arquivo
ou dados.
* @Lacuna.Pki.Cades.CadesSigner.SetPolicy(Lacuna.Pki.Cades.CadesPolicySpec) -
Define a política de assinatura. O SDK possui políticas já configuradas nos padrões de algumas PKIs, como a ICP-Brasil,
e também permite que você crie ou customize uma política própria. No exemplo foi passada uma política já configurada no
padrão ICP-Brasil AD-RT. Para mais informações sobre políticas de assinatura veja o artigo
[Políticas de assinatura](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies).
* @Lacuna.Pki.Cades.CadesSigner.SetTimestampRequester(Lacuna.Pki.ITimestampRequester) -
Define um requester para solicitar carimbos de tempo a uma carimbadora. A política de assinatura utilizada no exemplo
(ICP-Brasil AD-RT) requer o uso de carimbo de tempo, portanto é necessário passar um requester de carimbo de tempo
para o assinador.
## Assinatura remota
O assinador @Lacuna.Pki.Cades.CadesSigner também permite a criação de CAdES em que a assinatura é realizada remotamente,
por exemplo no browser do cliente. Para isso, o método a ser utilizado na definição do signatário não tem chave privada
associada: @Lacuna.Pki.Cades.CadesSigner.SetSigningCertificate(Lacuna.Pki.PKCertificate). Também serão utilizados os métodos
de gerar os dados a serem assinados pelo cliente
(`Lacuna.Pki.Cades.CadesSigner.GenerateToSignBytes(Lacuna.Pki.SignatureAlgorithm@)`)
e o método de incluir os dados assinados no CAdES
(`Lacuna.Pki.Cades.CadesSigner.SetPrecomputedSignature(System.Byte[])`).
Para mais informações sobre assinatura remota veja o artigo
[Assinatura com chave remota (assinatura no browser)](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/web-remote).
## Validação de Assinaturas
Com o PKI SDK é possível validar o pacote inteiro de assinaturas de acordo com uma única política:
```cs
CadesSignature signature = ...
Dictionary validationResults = signature.ValidateAllSignatures(CadesPoliciesForValidation.GetCadesBasic());
foreach (var item in validationResults) {
Console.WriteLine("Passed checks:");
item.Value.PassedChecks.ForEach(vi => Console.WriteLine(vi.Detail)); // imprime todas as validações bem-sucedidas
if (item.Value.HasErrors) { // caso tenha erros
Console.WriteLine("Errors:");
item.Value.Errors.ForEach(e => Console.WriteLine(e.Message)); //imprime o motivo pelo qual não passaram
}
}
```
Ou validar cada uma das assinaturas com uma política desejada:
```cs
var cadesSig = ...
var myIssuer = ...
CadesSignature signature = CadesSignature.Open(cadesSig);
foreach (var signerInfo in signature.Signers) {
var policyMapper = signerInfo.SigningCertificate.Issuer.Equals(myIssuer) // caso o certificado tenha sido emitido pelo emissor da minha empresa fora das raízes do Windows
? CadesPoliciesForValidation.GetCadesBasic(new CustomArbitrator()) // utiliza um trust arbitrator customizado que confia no meu emissor
: CadesPoliciesForValidation.GetCadesBasic(); // caso contrário utiliza as raízes do Windows como raízes confiáveis
ValidationResults result = signature.ValidateSignature(signerInfo, policyMapper); // valida a assinatura conforme a política especificada
Console.WriteLine("Passed checks:");
result.PassedChecks.ForEach(vi => Console.WriteLine(vi.Detail)); // imprime todas as validações bem-sucedidas
if (result.HasErrors) {
Console.WriteLine("Errors:");
result.Errors.ForEach(e => Console.WriteLine($"{e.Message} - {e.Detail}")); //imprime o motivo pelo qual não passaram
}
}
```
## Veja também
* @Lacuna.Pki.Cades.CadesSigner
* @Lacuna.Pki.PKCertificateWithKey
* @Lacuna.Pki.PKCertificate
* [Políticas de assinatura](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies)
* [Assinatura com chave remota (assinatura no browser)](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/web-remote)
**Demonstrações e exemplos**
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Sign a PDF with PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureSdkController.cs)
- Sign a file with CAdES using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesSignatureSdkController.cs)
- Validate a signed PDF using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenPadesSignatureSdkController.cs)
- Validate a CMS/CAdES signature using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenCadesSignatureSdkController.cs)
- Validate a signed XML using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenXmlSignatureSdkController.cs)
- Sign multiple files with CAdES using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/BatchCadesSignatureSdkController.cs)
- Sign a file with CAdES and a server key using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesServerKeySdkController.cs)
- Merge CMS/CAdES signatures using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/MergeCadesSdkController.cs)
- Make a printer-friendly version of a CMS/CAdES signature using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyCadesSdkController.cs)
# [Co-assinatura](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades/co-sign)
O PKI SDK permite que diferentes signatários que assinam um mesmo documento possam ser agrupados em um mesmo pacote de
assinatura. Esta modalidade de assinaturas em paralelo se chama co-assinatura e é prevista pelo padrão CAdES.
O exemplo abaixo demonstra uma co-assinatura básica:
```cs
// Assinatura CAdES básica prévia
byte[] cadesSig1 = ...
// Co-assinando a assinatura cadesSig1
PKCertificateWithKey signingCert2 = ...
var signer2 = new CadesSigner();
signer2.SetSigningCertificate(signingCert2);
signer2.SetDataToSign(toSign);
signer2.SetPolicy(policy);
// Definindo a assinatura prévia para co-assinatura
signer2.SetSignatureToCoSign(cadesSig1);
signer2.ComputeSignature();
byte[] cadesCoSig = signer2.GetSignature();
```
> **Observação**
>
> Caso a assinatura cadesSig1 já tenha o documento assinado no encapsulated-content, não é necessária a chamada do método
> @Lacuna.Pki.Cades.CadesSigner.SetDataToSign(System.Byte[]) na co-assinatura.
Caso se tenha assinaturas paralelas de um mesmo documento, sem que se tenha utilizado o método
@Lacuna.Pki.Cades.CadesSigner.SetSignatureToCoSign(System.Byte[]), também é possível fundir as assinaturas em uma
co-assinatura através do método
`Lacuna.Pki.Cades.CadesSignatureEditor.MergeSignatures(IEnumerable)`
da classe @Lacuna.Pki.Cades.CadesSignatureEditor.
## Veja também
* @Lacuna.Pki.Cades.CadesSigner
* @Lacuna.Pki.Cades.CadesSignatureEditor
# [Compressão de assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades/compression)
Métodos usuais de compressão de dados utilizados em assinaturas individualmente não oferecem um resultado satisfatório no
cenário de se armazenar grande quantidade de assinaturas. No entanto, é neste cenário que o método do PKI SDK tem
melhor resultado.
Ao usar compressão convencional de dados em cada assinatura individualmente, pouca redundância é encontrada. Ao considerar
as assinaturas armazenadas como um conjunto e tratá-las coletivamente, é possível observar grande redundância de CRLs,
certificados e outras estruturas que se repetem por várias assinaturas realizadas em um período semelhante.
Utilizando a compressão de assinaturas do SDK é possível atingir níveis de compressão superiores a 90%.
## Comprimindo assinatura
A compressão de assinatura é realizada através da classe @Lacuna.Pki.Cades.CadesSignatureCompression. Seus métodos de
compressão e descompressão utilizam uma implementação de um @Lacuna.Pki.Stores.ISimpleStore, que é responsável por
armazenar os dados que tendem a ser comuns entre as assinaturas. O código abaixo exemplifica a compressão de uma
assinatura com desanexação do encapsulated content:
```cs
// bytes de uma assinatura CAdES a ser comprimida
byte[] sigContent = ...
// Simple store baseado em um filesystem no diretório temporário
var store = new FileSystemSimpleStore(Path.GetTempPath());
byte[] eContent;
// Comprimindo assinatura com eContent detached
byte[] compressed = CadesSignatureCompression.Compress(sigContent, store, out eContent);
// Descomprimindo assinatura
byte[] decompressed = CadesSignatureCompression.Decompress(compressed, store, eContent);
if (sigContent.SequenceEqual(decompressed)) {
Console.WriteLine("OK!");
} else {
Console.WriteLine("NOT OK!");
}
```
Um elemento fundamental para a compressão de assinatura é o objeto @Lacuna.Pki.Stores.ISimpleStore. No exemplo acima, foi
utilizado um @Lacuna.Pki.Stores.FileSystemSimpleStore, que irá armazenar os dados da compressão no diretório passado na sua
instanciação. Caso deseje utilizar outras estruturas como banco de dados ou servidores na nuvem, basta implementar a
interface @Lacuna.Pki.Stores.ISimpleStore.
## Veja também
* @Lacuna.Pki.Cades.CadesSignatureCompression
* @Lacuna.Pki.Stores.ISimpleStore
* @Lacuna.Pki.Stores.FileSystemSimpleStore
* [Pacotes nuget opcionais](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages)
# [Finalização assíncrona de assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades/finisher)
A finalização assíncrona de assinaturas é uma abordagem oferecida pelo PKI SDK para um processo de assinatura mais confiável
e otimizado. O uso da finalização assíncrona visa as seguintes vantagens:
* Resistência a falhas na distribuição de CRLs/OCSPs e carimbos de tempo.
* Ganho de desempenho na parte síncrona do processo, pois não é mais necessário aguardar resposta de requisições de CRL,
OCSP ou carimbo de tempo.
* Uso de políticas de assinatura com grace period.
Deste modo, a assinatura do cliente é solicitada apenas uma vez e é salva, encerrando-se o processo com o usuário. Em um
outro momento, através de um *background worker* por exemplo, as requisições e verificações online ou em cache são realizadas
e a assinatura é finalizada. Caso haja problemas neste segundo passo, não é necessário solicitar novamente a assinatura do
cliente.
## Finalizador de assinaturas
A finalização de assinatura é gerenciada pela classe @Lacuna.Pki.Cades.CadesSignatureFinisher. Abaixo temos um exemplo de
uso do finalizador e em seguida um detalhamento dos métodos utilizados.
Em um primeiro momento, criamos uma assinatura não finalizada:
```cs
// Assinatura CAdES básica
var signer = new CadesSigner();
signer.SetSigningCertificate(signingCert);
signer.SetDataToSign(toSign);
signer.SetPolicy(policy);
// Antes de executar o ComputeSignature(),
// defina que a assinatura não será finalizada
signer.SetFinishSignature(false);
signer.ComputeSignature();
var cadesSig = signer.GetSignature(); // retorna pacote de assinatura não finalizado
// Salvar a assinatura não finalizada e encerrar o processo
```
Em um segundo momento, através de um *background worker* (serviço do Windows ou task agendada), finalizamos a assinatura:
```cs
// Carregando assinatura não finalizada
byte[] cadesSig = ...
// Instanciando o finalizador com a assinatura não finalizada
var finisher = new CadesSignatureFinisher(CadesSignature.Open(cadesSig));
finisher.SetPolicy(policy);
finisher.SetTimestampRequester(tsRequester); // requester do carimbo de tempo (se a política exigir carimbo)
finisher.UseOnlyCrlsIssuedAfterGracePeriod = true; // usar apenas valores de revogação emitidos após o fim do grace period
finisher.PerformStep();
if (finisher.IsSignatureFinished) {
// Assinatura finalizada
// Retorna os bytes da assinatura finalizada com encoding BER
var finishedCadesSig = finisher.GetSignatureEncoded();
} else {
// Retorna o delay sugerido até a chamada novamente do serviço de finalização com PerformStep()
var delay = finisher.NextStepDelay.Value;
// ou
// Retorna a hora sugerida para a próxima chamada ao serviço de finalização com PerformStep()
var time = finisher.NextStepTime.Value;
}
```
* @Lacuna.Pki.Cades.CadesSignatureFinisher.SetPolicy(Lacuna.Pki.Cades.CadesPolicySpec) /
@Lacuna.Pki.Cades.CadesSignatureFinisher.SetPolicyMapper(Lacuna.Pki.Cades.ICadesPolicyMapper) /
@Lacuna.Pki.Cades.CadesSignatureFinisher.SetPolicyMapper(Lacuna.Pki.Cades.ICadesPolicyMapperBySignerInfo) -
Usados para definir a política de assinatura para finalização. Ideal que seja a mesma política utilizada no assinador,
mas neste segundo momento apenas as propriedades de carimbos de tempo, valores e referências de revogação e certificados
serão utilizadas.
* @Lacuna.Pki.Cades.CadesSignatureFinisher.SetTimestampRequester(Lacuna.Pki.ITimestampRequester) -
Usado se a política de assinatura exigir carimbo de tempo.
* @Lacuna.Pki.Cades.CadesSignatureFinisher.UseOnlyCrlsIssuedAfterGracePeriod -
Exige que os valores de revogação a serem verificados tenham sido emitidos após o grace period. Para mais informações
sobre grace period veja o artigo [Políticas com grace period](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/grace-period).
* @Lacuna.Pki.Cades.CadesSignatureFinisher.PerformStep -
Este método é o motor do finalizador de assinatura, é responsável por buscar os elementos necessários para finalizar
a assinatura e, se houver política de grace period, informa novo momento para sua execução caso o grace period não
tenha encerrado.
## Veja também
* @Lacuna.Pki.Cades.CadesSignatureFinisher
# [Assinaturas padrão PAdES](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades)
O padrão PAdES especifica regras para assinaturas de arquivos PDF. No SDK a classe @Lacuna.Pki.Pades.PadesSigner é o
objeto central responsável pela criação de assinaturas em PDF seguindo a especificação ETSI 102778-x.
## Exemplo de assinatura básica
O código abaixo exemplifica uma assinatura PAdES básica com o objetivo de apresentar e exercitar os conceitos e
propriedades desse assinador.
```cs
// Instanciamos o objeto signer que é responsável por criar a assinatura
var signer = new PadesSigner();
signer.SetSigningCertificate(signingCert); // certificado do signatário com chave privada associada
signer.SetPdfToSign(pdfBytes); // bytes ou stream do documento PDF a ser assinado
signer.SetPolicy(PadesPolicySpec.GetBasic()); // política básica com raízes do Windows como TrustArbitrators
signer.ComputeSignature(); // cria atributos e colhe assinatura do signatário
byte[] signedPdf = signer.GetPadesSignature(); // Retorna os bytes do PDF assinado
```
**Demonstrações e exemplos**
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
- Sign a PDF with PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureSdkController.cs)
- Sign a file with CAdES using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/CadesSignatureSdkController.cs)
- Validate a signed PDF using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenPadesSignatureSdkController.cs)
- Validate a CMS/CAdES signature using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenCadesSignatureSdkController.cs)
- Validate a signed XML using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenXmlSignatureSdkController.cs)
- Sign multiple PDFs with PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/BatchPadesSignatureSdkController.cs)
- Sign a PDF with a server key using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesServerKeySdkController.cs)
- Make a printer-friendly version of a signed PDF using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyPadesSdkController.cs)
- Sign a medical prescription (Receita Simples) using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/ReceitaSimplesSdkController.cs)
# [Validação do Adobe Reader](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades/adobe-validation)
O Adobe Reader é instalado com a configuração padrão de confiar apenas em sua própria lista de certificados raízes
confiáveis. Por esse motivo, assinaturas feitas com alguns certificados podem ser interpretadas como não confiáveis em
leitores com a configuração padrão, por exemplo certificados da cadeia V5 da ICP-Brasil.
Geralmente orientamos os usuários a configurarem seus leitores para aceitar também a lista de certificados raízes do
Windows. O link abaixo contém um passo-a-passo de como fazer essa configuração:
[Configuring Adobe Reader to trust Windows roots for signature validation](https://docs.lacunasoftware.com/en-us/content/configuring-windows-roots-on-adobe-reader/)
Caso queira distribuir essa configuração via *Group Policy Object* (GPO), a alteração necessária no *Registry* é a seguinte:
```ini
[HKEY_CURRENT_USER\Software\Adobe\Acrobat Reader\DC\Security\cASPKI\cMSCAPI_DirectoryProvider]
"iMSStoreTrusted"=dword:00000060
```
[Clique aqui](https://docs.lacunasoftware.com/en-us/content/enable-adobe-trust-on-windows-roots.zip) para obter um arquivo .reg que realiza a alteração acima.
# [Assinaturas em série](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades/co-sign)
A assinatura em série é o mecanismo para multi-assinaturas no padrão PAdES. Após uma primeira assinatura do documento PDF,
o fluxo da assinatura em série deve passar para o próximo signatário, que assina não somente o documento, mas o documento
contendo também a assinatura anterior e assim por diante.
Para realizar uma assinatura PAdES em série no SDK não há nenhuma mudança, basta passar um PDF já assinado para o objeto
assinador do próximo signatário. O exemplo a seguir demonstra uma assinatura em série com um PDF já assinado:
```cs
// Documento PDF já assinado
byte[] signedPdf1 = ...
// Certificado do signatário 2
PKCertificateWithKey signingCert2 = ...
var signer2 = new PadesSigner();
signer2.SetPdfToSign(signedPdf1); // PDF assinado 1
signer2.SetSigningCertificate(signingCert2);
signer2.SetPolicy(policy);
signer2.ComputeSignature();
byte[] signedPdf2 = signer2.GetPadesSignature(); // Retornando PDF assinado contendo 2 assinaturas
```
## Veja também
* @Lacuna.Pki.Pades.PadesSigner
# [LTV (Long Term Validation)](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades/ltv)
LTV é um mecanismo utilizado em assinaturas PAdES para torná-las válidas por um longo período. Funciona como uma espécie
de arquivamento em que as informações como certificados e estados de revogação são armazenadas em uma estrutura do PDF
chamada DSS. Ao final, é recomendado que seja adicionado um timestamp protegendo estas informações e podendo estender a
validade das assinaturas até mesmo após a expiração dos certificados das Autoridades Certificadoras.
O PKI SDK permite adicionar informações LTV tanto no momento da assinatura quanto em um segundo passo utilizando o
carimbador de tempo @Lacuna.Pki.Pades.PadesTimestamper. Note que, como explicado acima, a habilitação do LTV com carimbo
de tempo é o método mais robusto, pois também pode ser utilizado para estender a validade de assinaturas anteriores ou
também de carimbos de tempo anteriores. Abaixo demonstramos os dois métodos citados.
No primeiro exemplo abaixo, habilitamos o LTV no momento da assinatura (sem carimbo de tempo):
```cs
// habilitando LTV na política de assinatura
var policy = PadesPolicySpec.GetBasic(TrustArbitrators.Windows);
policy.SignerSpecs.AttributeGeneration.EnableLtv = true;
var signer = new PadesSigner();
signer.SetSigningCertificate(signingCert);
signer.SetPdfToSign(pdfBytes);
signer.SetPolicy(policy);
signer.ComputeSignature();
byte[] signedPdf = signer.GetPadesSignature(); // Retorna os bytes do PDF assinado com LTV habilitado
```
Neste segundo exemplo habilitamos LTV em uma assinatura já existente utilizando o carimbador de PDF
@Lacuna.Pki.Pades.PadesTimestamper:
```cs
// PDF já assinado
byte[] signedPdf = ...
// a política padrão de carimbo já possui LTV habilitado,
// iremos habilitá-lo novamente apenas para demonstração de onde é localizada esta propriedade
var policy = PadesPolicySpec.GetBasic();
policy.StamperSpecs.EnableLtv = true;
// Instanciando carimbador de PDF com LTV
var stamper = new PadesTimestamper();
stamper.SetPdf(signedPdf);
stamper.SetPolicy(policy);
stamper.SetTimestampRequester(tsRequester); // requester do carimbo de tempo
stamper.Stamp();
// Retornando PDF carimbado com LTV habilitado
byte[] signedPdfLtv = stamper.GetStampedSignature();
```
## Veja também
* @Lacuna.Pki.Pades.PadesTimestamper
* @Lacuna.Pki.Pades.PadesPolicySpec
# [Representação Visual](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades/visual-rep)
A representação visual na assinatura de PDF é a inclusão de um texto ou imagem contendo informações sobre a assinatura
que está interna ao documento. Dependendo do software de visualização do PDF, esta representação pode fazer uma ligação
com os dados e propriedades da assinatura e do signatário.
**Cada representação visual é sempre relativa a apenas uma assinatura no PDF e cada assinatura pode ter até uma representação visual.**
As informações da representação visual a serem incluídas na assinatura são controladas pela classe
@Lacuna.Pki.Pades.PadesVisualRepresentation2. Nas seções abaixo exploraremos as opções, parâmetros e customizações na
definição de uma representação visual para assinaturas em PDF.
## Elementos da Representação Visual
A classe de representação visual @Lacuna.Pki.Pades.PadesVisualRepresentation2 possui 3 elementos que podem ser definidos:
@Lacuna.Pki.Pades.PadesVisualRepresentation2.Position, @Lacuna.Pki.Pades.PadesVisualRepresentation2.Text e
@Lacuna.Pki.Pades.PadesVisualRepresentation2.Image. A lista abaixo introduz os elementos da representação visual.
Para mais informações verifique o guia da API de cada classe.
* Posição
A posição do retângulo da representação visual da assinatura é customizável através das classes
@Lacuna.Pki.Pades.PadesVisualAutoPositioning e @Lacuna.Pki.Pades.PadesVisualManualPositioning.
* Posicionamento Automático
O posicionamento automático é indicado para casos em que pode-se definir uma região (container) em uma página do PDF
que irá receber a representação das assinaturas. Este modo irá organizar as assinaturas em série dentro da região
especificada. A figura abaixo exemplifica o comportamento do posicionamento automático. O retângulo vermelho representa
o container definido no qual as representações visuais serão posicionadas automaticamente em série.

* Posicionamento Manual
O posicionamento manual é indicado para os casos em que cada representação das assinaturas serão inseridas em posições
arbitrárias, por exemplo a posição e a página serem informados pelo usuário.

* Texto
As informações textuais a serem inseridas na representação visual da assinatura são customizáveis através da classe
@Lacuna.Pki.Pades.PadesVisualText.
* Imagem
É possível incluir uma imagem de fundo na representação visual da assinatura, podendo também customizar sua opacidade
e seu alinhamento dentro do retângulo da representação visual. Estas opções são definidas através da classe
@Lacuna.Pki.Pades.PadesVisualImage.
## Posicionamento Manual
No posicionamento manual, define-se a página na qual a representação da assinatura será inserida e a posição e tamanho
do retângulo de assinatura em relação à página.
A página é especificada pela propriedade @Lacuna.Pki.Pades.PadesVisualPositioning.PageNumber. Números negativos são
contados a partir do final do documento (-1 significa última página, -2 penúltima, etc). Pode-se usar o valor especial
0 (zero), que especifica que a representação da assinatura deve ser colocada em uma nova página em branco adicionada
ao final do documento.
A posição do retângulo de assinatura em relação à página é especificada pela propriedade
@Lacuna.Pki.Pades.PadesVisualManualPositioning.SignatureRectangle. Um retângulo pode ter os parâmetros `Top`, `Bottom`,
`Left`, `Right`, `Width` e `Height` conforme mostrado na figura abaixo.

Por exemplo, para especificar que a representação visual deve ser inserida na última página do documento, tendo tamanho
7cm de largura e 3cm de altura, ficando a 2.5cm das margens esquerda e inferior, conforme a imagem abaixo, escreveríamos
o seguinte código:

```cs
var signer = new PadesSigner();
...
var signatureVisualRep = new PadesVisualRepresentation2() {
Position = new PadesVisualManualPositioning() {
PageNumber = -1, // Define inserção na última página do documento
SignatureRectangle = new PadesVisualRectangle() {
Width = 7, // Largura = 7cm
Height = 3, // Altura = 3cm
Left = 2.50, // Distância da margem esquerda = 2.50cm
Bottom = 2.50 // Distância da margem inferior = 2.50cm
}
},
Text = ...,
Image = ...
};
signer.SetVisualRepresentation(signatureVisualRep);
```
Para mais informações sobre como definir a posição da representação da assinatura, veja o artigo
[Definindo um container](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades/visual-rep/containers).
## Posicionamento Automático
No posicionamento automático, assim como no manual, é preciso especificar a página na qual a representação visual será
inserida. Entretanto, ao invés de se definir o posicionamento da representação visual em relação à página, define-se um
container que irá receber as representações das sucessivas assinaturas realizadas no documento e o tamanho de cada
retângulo de assinatura dentro do container.
O container de assinaturas é definido pela propriedade @Lacuna.Pki.Pades.PadesVisualAutoPositioning.Container. De
maneira similar à especificação do retângulo de assinatura no posicionamento manual, o container pode ter os parâmetros
`Top`, `Bottom`, `Left`, `Right`, `Width` e `Height`. Já o tamanho de cada retângulo de assinatura (altura e largura) é
definido pela propriedade @Lacuna.Pki.Pades.PadesVisualAutoPositioning.SignatureRectangleSize.
A propriedade @Lacuna.Pki.Pades.PadesVisualAutoPositioning.RowSpacing define o espaçamento entre as "linhas" das
representações visuais. Os retângulos de assinaturas das representações visuais são inseridos da esquerda para direita e
quando não há mais espaço, são inseridas em uma nova linha respeitando o `RowSpacing` passado.
Por exemplo, para especificar que as representações visuais devem ser inseridas sucessivamente na última página do
documento dentro de um container espaçado 3cm das bordas esquerda e direita, 2cm da borda inferior e 8cm da borda superior, sendo
que cada retângulo de assinatura deve ter 7cm de largura por 3cm de altura, conforme ilustrado na imagem abaixo,
escreveríamos o seguinte código:

```cs
var signer = new PadesSigner();
...
var signatureVisualRep = new PadesVisualRepresentation2() {
Position = new PadesVisualAutoPositioning() {
PageNumber = -1, // Define inserção na última página do documento
Container = new PadesVisualRectangle() {
Left = 3.00, // Distância da margem esquerda = 3.00cm
Right = 3.00, // Distância da margem direita = 3.00cm
Bottom = 2.00, // Distância da margem inferior = 2.00cm
Top = 8.00 // Distância da margem superior = 8.00cm
},
SignatureRectangleSize = new PadesSize(7, 3), // Tamanho de cada retângulo de assinatura (largura x altura),
RowSpacing = 1 // 1.00cm de espaçamento entre linhas
},
Text = ...,
Image = ...
};
signer.SetVisualRepresentation(signatureVisualRep);
```
> **Observação**
>
> O container que definimos acima tem altura e largura variáveis. Essa é uma das muitas possibilidades de especificação
> do container de assinaturas. Para mais informações, veja o artigo [Definindo um container](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades/visual-rep/containers).
As assinaturas realizadas com o código acima terão o resultado esperado conforme a imagem abaixo:

## Posicionamento de texto e imagem
* Texto
Interno ao retângulo da representação visual da assinatura, o SDK permite a customização do alinhamento, posição e
conteúdo do texto a ser inserido. O posicionamento do texto pode ser controlado através da definição de um
@Lacuna.Pki.Pades.PadesVisualText.Container. Se nenhum container for definido, o texto preencherá todo o retângulo
de assinatura.
Como exemplo, definiremos um posicionamento do texto relativo ao retângulo de assinatura conforme a figura abaixo.

```cs
var text = new PadesVisualText() { // Texto da representação visual
SignerName = signingCert.Certificate.SubjectDisplayName, // Inclui o nome do signatário
IncludeSigningTime = true, // Inclui a data da assinatura
Container = new PadesVisualRectangle() { // Define container do texto
Left = 0,
Top = 0,
Right = 1.5,
Bottom = 0.5
}
};
```
Verifique as customizações de texto possíveis nas propriedades no guia da API @Lacuna.Pki.Pades.PadesVisualText.
* Imagem
Também é possível definir uma imagem de fundo para o retângulo de assinatura e customizar sua opacidade e alinhamento.
Verifique as customizações de imagem possíveis nas propriedades no guia da API @Lacuna.Pki.Pades.PadesVisualImage.
## Veja também
* @Lacuna.Pki.Pades.PadesVisualRepresentation2
* @Lacuna.Pki.Pades.PadesVisualText
* @Lacuna.Pki.Pades.PadesVisualImage
* @Lacuna.Pki.Pades.PadesVisualAutoPositioning
* @Lacuna.Pki.Pades.PadesVisualManualPositioning
# [Definindo um container](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades/visual-rep/containers)
Na representação visual da assinatura PAdES os retângulos e containers são definidos através da classe
@Lacuna.Pki.Pades.PadesVisualRectangle.
## Container
Um container possui os parâmetros `Top`, `Bottom`, `Left`, `Right`, `Width` e `Height` conforme na figura abaixo.

O container, em vermelho, possui suas margens relativas ao elemento no qual ele está sendo inserido.
Para instanciar um container, no entanto, não se deve preencher todos os seus parâmetros. A definição de um conjunto de
parâmetros específicos produz diferentes efeitos. Exemplificaremos a seguir as possibilidades para se definir as dimensões
horizontais de um container (as dimensões verticais são definidas analogamente como explicado ao final dos exemplos).
### Fluido
Definindo apenas os parâmetros `Left` e `Right`, o container respeitará a dimensão passada para essas margens e sua
largura (`Width`) irá se esticar de acordo com o espaço horizontal restante disponível como ilustrado na figura abaixo.

### Ancorado à esquerda
Definindo apenas os parâmetros `Left` e `Width`, o container respeitará as dimensões passadas para a margem esquerda e
a largura. A margem direita terá seu tamanho dependente do espaço horizontal restante disponível como ilustrado na figura
abaixo.

### Ancorado à direita
Definindo apenas os parâmetros `Right` e `Width`, o container respeitará as dimensões passadas para a margem direita e
a largura. A margem esquerda terá seu tamanho dependente do espaço horizontal restante disponível como ilustrado na figura
abaixo.

### Centralizado
Definindo apenas o parâmetro `Width`, o container será inserido centralizado horizontalmente respeitando a dimensão da
largura passada, conforme ilustrado na figura abaixo.

## Dimensões verticais
A definição das dimensões verticais seguem analogamente as instruções das dimensões horizontais discutidas acima,
bastando substituir `Left` e `Right` por `Top` e `Bottom` e `Width` por `Height`.
## Veja também
* @Lacuna.Pki.Pades.PadesVisualRectangle
# [Exemplos completos](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades/visual-rep/samples)
## Exemplo completo com posicionamento automático padrão
O PKI SDK oferece dois templates para o posicionamento automático das representações visuais: **Rodapé** e **Nova Página**.
O método @Lacuna.Pki.Pades.PadesVisualAutoPositioning.GetFootnote(System.Int32,System.Int32) retorna um posicionamento que
organiza automaticamente as assinaturas no rodapé da página.
O método @Lacuna.Pki.Pades.PadesVisualAutoPositioning.GetNewPage retorna um posicionamento que insere uma nova página em
branco ao final do documento e organiza automaticamente as assinaturas em grid nesta página adicionada.
O exemplo de código abaixo demonstra uma assinatura básica com representação visual com posicionamento automático padrão
rodapé.
```cs
var signer = new PadesSigner();
signer.SetSigningCertificate(cert);
signer.SetPdfToSign(pdfBytes);
signer.SetPolicy(PadesPolicySpec.GetBasic(TrustArbitrators.Windows));
// Criando objeto de representação visual
var visual = new PadesVisualRepresentation2() {
Position = PadesVisualAutoPositioning.GetFootnote(), // Define posicionamento automático no rodapé da última página
Text = new PadesVisualText() { // Texto da representação visual
SignerName = cert.Certificate.SubjectDisplayName, // Inclui o nome do signatário
IncludeSigningTime = true // Inclui a data da assinatura
},
Image = new PadesVisualImage() { // Imagem de fundo na representação visual
Content = getPdfStampContent(), // Bytes da imagem a ser inserida
HorizontalAlign = PadesHorizontalAlign.Right // Define imagem alinhada à direita no retângulo da representação visual
}
};
signer.SetVisualRepresentation(visual); // Define representação visual na assinatura
signer.ComputeSignature();
byte[] signedPdf = signer.GetPadesSignature(); // Retorna os bytes do PDF assinado com
// representação visual
```
O processo acima deve produzir um PDF assinado contendo uma representação visual no rodapé da última página do documento,
com nome do signatário, data da assinatura e uma imagem de marca d'água conforme a figura a seguir.

## Exemplo completo com posicionamento automático customizado
Criaremos um posicionamento automático customizado que será aplicado na última página do documento.
O container de inclusão, região na qual as representações visuais das assinaturas serão organizadas automaticamente,
será definido seguindo o modelo representado na imagem abaixo.

O código abaixo exemplifica como definir estes parâmetros em um container.
```cs
var container = new PadesVisualRectangle() {
Left = 3.00,
Right = 3.00,
Bottom = 2.00,
Top = 8.00
};
```
Para mais informações de como definir um Container, veja o artigo [Definindo um container](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades/visual-rep/containers)
Cada assinatura incluída terá sua representação visual seguindo os parâmetros do retângulo de assinatura, texto e imagem
seguindo o modelo representado pela imagem abaixo.

O retângulo externo da assinatura terá 7cm de largura por 3cm de altura. Será incluído um brasão de imagem de fundo alinhada à direita no
retângulo de assinatura. O container do texto terá uma margem direita de 1.50cm em relação ao retângulo externo,
impedindo, para este exemplo, que o texto fique por cima da imagem. O código abaixo demonstra como definir estes parâmetros.
```cs
// Definindo retângulo de assinatura
var signatureRectangleSize = new PadesSize(7, 3);
// Definindo o container do Texto
var textContainer = new PadesVisualRectangle() {
Left = 0,
Right = 1.5,
Bottom = 0,
Top = 0
};
// Definindo imagem de fundo
var image = new PadesVisualImage() {
Content = getPdfStampContent(), // Bytes da imagem a ser inserida
HorizontalAlign = PadesHorizontalAlign.Right // Define imagem alinhada à direita no retângulo da representação visual
};
```
À medida que as assinaturas forem sendo realizadas, o resultado esperado para as representações visuais incluídas
automaticamente é o apresentado na imagem abaixo.

### Código de exemplo completo
```cs
var signer = new PadesSigner();
signer.SetSigningCertificate(cert);
signer.SetPdfToSign(pdfBytes);
signer.SetPolicy(PadesPolicySpec.GetBasic(TrustArbitrators.Windows));
// Criando objeto de representação visual
var visual = new PadesVisualRepresentation2() {
// Define container para posicionamento automático das representações visuais
Position = new PadesVisualAutoPositioning() {
MeasurementUnits = PadesMeasurementUnits.Centimeters, // Unidade de medida == Cm
PageNumber = -1, // Container será aplicado na última página do documento
SignatureRectangleSize = new PadesSize(7, 3), // Define dimensões do retângulo de assinatura
Container = new PadesVisualRectangle() { // Define dimensões do container para posicionamento automático
Left = 3.0,
Right = 3.0,
Bottom = 2.0,
Top = 8.0
},
RowSpacing = 1.5 // Espaçamento entre as fileiras verticais de representações visuais
},
Text = new PadesVisualText() { // Texto da representação visual
SignerName = cert.Certificate.SubjectDisplayName, // Inclui o nome do signatário
IncludeSigningTime = true, // Inclui a data da assinatura
Container = new PadesVisualRectangle() { // Define container do texto
Left = 0,
Top = 0,
Right = 1.5,
Bottom = 0
}
},
Image = new PadesVisualImage() { // Imagem de fundo na representação visual
Content = getPdfStampContent(), // Bytes da imagem a ser inserida
HorizontalAlign = PadesHorizontalAlign.Right // Define imagem alinhada à direita no retângulo da representação visual
}
};
signer.SetVisualRepresentation(visual); // Define representação visual na assinatura
signer.ComputeSignature();
byte[] signedPdf = signer.GetPadesSignature(); // Retorna os bytes do PDF assinado com
// representação visual
```
A execução do exemplo acima produz o seguinte resultado na assinatura de um PDF:

# [Políticas de assinatura](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies)
Políticas de assinaturas são um conjunto de normas e propriedades que regulam a criação e validação de assinaturas
digitais. O PKI SDK possui padrões de políticas já configuradas para uso nos assinadores e verificadores, mas também é
possível de maneira simples criar sua própria política ou customizar um padrão já existente.
Quando uma política de assinatura sendo validada não é informada pelo usuário e também não pode ser inferida pelos dados
da assinatura ou está incompleta, os verificadores e árbitros podem obter resultados incorretos na validação. Portanto,
políticas de assinatura explícitas ou implícitas são fundamentais para garantir a consistência de dados tanto na
assinatura quanto na validação.
Veja os seguintes artigos para mais informações:
* [Mapeadores de política (Policy mappers)](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/mappers)
* [Políticas ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/pki-brazil)
* [Ciclo de vida da assinatura AD-RT](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/adrt-lifecycle)
* [Customização de políticas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/customization)
* [Políticas com referências, mas sem valores](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/revocation-refs-without-values)
* [Políticas com grace period](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/grace-period)
# [Ciclo de vida da assinatura AD-RT](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/adrt-lifecycle)
Uma assinatura **AD-RT** da ICP-Brasil (Referência de Tempo) é uma assinatura básica (AD-RB) com um **carimbo de tempo da assinatura**,
emitido por uma autoridade de carimbo de tempo (ACT) ICP-Brasil. O carimbo prova quando a assinatura foi feita, por isso a assinatura
sobrevive ao certificado do signatário. Mas ela não sobrevive ao certificado da ACT. Este artigo mostra por quanto tempo uma assinatura
AD-RT continua verificável e como o PKI SDK a mantém verificável por mais tempo, com as políticas AD-RC e AD-RA.

Para um caso real, com assinaturas que precisam durar décadas, veja [Revalidação de diplomas](https://docs.lacunasoftware.com/articles/digiploma/revalidation), que
mostra como o [Digiploma](https://docs.lacunasoftware.com/articles/digiploma) mantém os diplomas digitais verificáveis.
## Criar uma assinatura AD-RT
A política AD-RT exige um carimbo de tempo da assinatura, por isso o assinador precisa de um requisitante de carimbo de tempo. O exemplo
abaixo assina um elemento de um documento XML (XAdES):
```cs
var signer = new XmlElementSigner();
signer.SetSigningCertificate(cert); // certificado do signatário, com a chave privada
signer.SetXml(xmlDocument);
signer.SetToSignElementId(id);
signer.SetPolicy(XmlPoliciesForGeneration.GetPkiBrazilAdrTempo());
// autoridade de carimbo de tempo (ACT) ICP-Brasil
var tsaUri = new Uri("https://act.exemplo.com.br/");
signer.SetTimestampRequester(new TimestampRequester(tsaUri, accessToken));
signer.ComputeSignature(); // lança TimestampException se não conseguir o carimbo
var signedXml = signer.GetSignedXml();
```
A mesma política existe para os outros formatos: `CadesPoliciesForGeneration.GetPkiBrazilAdrTempo()` (CAdES) e
`PadesPoliciesForGeneration.GetPkiBrazilAdrTempo()` (PAdES).
## Por quanto tempo ela continua verificável
| Período | Verificável? | Por quê |
|---|---|---|
| Enquanto o certificado do signatário for válido | Sim | Como qualquer assinatura. |
| Depois que o certificado do signatário expira | Sim | O carimbo de tempo da assinatura prova que ela foi feita enquanto o certificado era válido. |
| Depois que o certificado da ACT expira | Não | O próprio carimbo de tempo da assinatura não pode mais ser verificado. |
Uma assinatura AD-RT também não guarda os certificados e as LCRs usados na validação: validá-la mais tarde depende de obtê-los nos
repositórios das autoridades certificadoras.
Para saber até quando uma assinatura AD-RT pode ser verificada, leia o certificado do carimbo de tempo da assinatura:
```cs
var signature = XmlSignatureEditor.Open(signedXml).Signatures.First();
var timestamp = signature.SignatureTimestamps.Last().EncapsulatedTimestamp;
var verifiableUntil = timestamp.Signers.First().SigningCertificate.ValidityEnd;
```
## Manter a assinatura verificável
Cada política ICP-Brasil acrescenta evidências à anterior:
* **AD-RC** (Referências Completas, `XmlPoliciesForGeneration.GetPkiBrazilAdrCompleta()`) acrescenta as referências e os valores dos
certificados e das LCRs usados na validação, e um carimbo de tempo sobre a assinatura e essas referências. Ela não depende mais dos
repositórios das autoridades certificadoras, mas os carimbos dela também expiram.
* **AD-RA** (Referências para Arquivamento, `XmlPoliciesForGeneration.GetPkiBrazilAdrArquivamento()`) acrescenta um **carimbo de tempo
de arquivamento**. Antes que ele expire, um novo carimbo de arquivamento, emitido com um certificado mais recente da ACT, é acrescentado
sobre tudo. Renovada a tempo, a assinatura continua verificável indefinidamente.
O `XmlSignatureEditor.ExtendArchiving` acrescenta esse novo carimbo de arquivamento. Ele primeiro registra os dados de validação do carimbo
de arquivamento anterior e depois carimba a assinatura de novo:
```cs
var editor = XmlSignatureEditor.Open(signedXml);
var signature = editor.Signatures.First(s => s.ArchiveTimestamps.Any());
var expiresOn = signature.ArchiveTimestamps.Last().EncapsulatedTimestamp.Signers.First().SigningCertificate.ValidityEnd;
if (expiresOn < DateTimeOffset.Now.AddDays(180)) {
var renewedXml = editor.ExtendArchiving(signature, timestampRequester, XmlPoliciesForValidation.GetPkiBrazil());
// Guarde renewedXml no lugar do arquivo anterior
}
```
O `ExtendArchiving` lança `InvalidArchiveOperationException` quando:
* a assinatura não é válida;
* nenhum dos carimbos de arquivamento dela pode ser validado;
* o novo carimbo é assinado pelo mesmo certificado de um carimbo de arquivamento anterior, o que não estenderia o vencimento. Passe
`ignoreSameTsSigner: true` para aceitá-lo mesmo assim.
> **Cuidado: Assinaturas AD-RT e AD-RC não podem ser estendidas**
>
> O `ExtendArchiving` exige uma política com carimbo de arquivamento: numa assinatura AD-RT ou AD-RC, ele falha. Para preservar essas
> assinaturas, proteja-as com uma assinatura AD-RA sobre o documento inteiro, que as cobre, e renove o carimbo de arquivamento dela. É assim
> que funciona um diploma digital: as assinaturas dos elementos `DadosDiploma` e `DadosRegistro` são cobertas pela assinatura de
> arquivamento do XML inteiro.
## Veja também
* [Revalidação de diplomas](https://docs.lacunasoftware.com/articles/digiploma/revalidation) (Digiploma)
* [Políticas ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/pki-brazil)
* [XmlSignatureEditor](https://docs.lacunasoftware.com/api/Lacuna.Pki.Xml/XmlSignatureEditor)
* [XmlPoliciesForGeneration](https://docs.lacunasoftware.com/api/Lacuna.Pki.Xml/XmlPoliciesForGeneration)
# [Customização de políticas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/customization)
Políticas de assinaturas são um conjunto de normas e propriedades que regulam a criação e validação de assinaturas
digitais. O PKI SDK possui padrões de políticas já configuradas para uso nos assinadores e verificadores, mas também
é possível de maneira simples criar sua própria política ou customizar um padrão já existente.
## Editando uma política
O exemplo abaixo demonstra como carregar um padrão de política de assinatura já configurado e editar alguns parâmetros.
```cs
// Carregando política AD-RT v2.1
var policy = BrazilCadesPolicySpec.GetAdrTempoV21();
// Definindo a inclusão do atributo assinado signing-time
policy.AttributeGeneration.IncludeSigningTime = true;
// Adicionando mais um árbitro para validação do certificado dos signatários
policy.AddTrustArbitrator(TrustArbitrators.Windows);
// Adicionando mais um árbitro para validação do certificado da carimbadora
policy.SignatureTimestampPolicy.AddTrustArbitrator(new TrustedRoots(privateRoot));
```
Caso esteja fazendo edição de uma política, certifique-se de não criar propriedades conflitantes na criação e validação.
## Criando uma política
No exemplo abaixo iremos criar uma política de assinatura CAdES-BES básica implícita, a política não é assinada, não
faz parte dos atributos assinados.
```cs
// Criando política
var policy = new CadesPolicySpec();
// Definindo propriedades de criação da assinatura
policy.AttributeGeneration = new AttributeGenerationSpec() {
IncludeSigningTime = true, // Inclui signing-time nos atributos assinados
IncludeCertificatesInSignedData = InclusionLevel.WholeChain, // Inclui toda cadeia do certificado no campo Certificates do SignedData
IncludeCrlsInSignedData = true, // Inclui as CRLs usadas no campo CRLs do SignedData
};
// Definindo propriedades de validação da assinatura básica
policy.AttributeValidation = new AttributeValidationSpec() {
SigningTime = ValidationRequirementLevels.May,
};
// Algoritmos permitidos
policy.DigestAlgorithms.Add(DigestAlgorithm.SHA256);
policy.DigestAlgorithms.Add(DigestAlgorithm.SHA1);
policy.SignatureAlgorithms.Add(SignatureAlgorithm.RSAWithSHA256);
policy.SignatureAlgorithms.Add(SignatureAlgorithm.RSAWithSHA1);
// Árbitro para validação dos certificados dos signatários
policy.AddTrustArbitrator(TrustArbitrators.Windows);
return policy;
```
## Criando uma política avançada
O código abaixo foi retirado de uma implementação interna do SDK para exemplificar a criação de uma política de
assinatura com elementos avançados. A política em questão é a do padrão ICP-Brasil AD-RC v2.1.
```cs
// Criando política
var policy = new CadesPolicySpec();
// Definindo propriedades e atributos de criação da assinatura
policy.AttributeGeneration = new AttributeGenerationSpec() {
IncludeFullPathReferenceInSigningCertificateAttribute = false,
// Inclui a política nos atributos assinados (política explícita)
IncludeSignaturePolicyIdentifier = true,
SignaturePolicyQualifiers = CadesPolicySpec.SignaturePolicyQualifiers.Uri,
SignatureTimestamp = GenerationRequirementLevels.Must,
IncludeCompleteCertificateReferences = true,
IncludeCompleteRevocationReferences = true,
CadesCTimeStamp = GenerationRequirementLevels.Must,
IncludeCertificateValues = true,
IncludeRevocationValues = true,
};
// Definindo propriedades e presença de atributos para validação
policy.AttributeValidation = new AttributeValidationSpec() {
SignaturePolicyIdentifier = ValidationRequirementLevels.Must,
SigningTime = ValidationRequirementLevels.May,
SignatureTimestamp = ValidationRequirementLevels.Must,
CompleteCertificateReferences = ValidationRequirementLevels.Must,
CompleteRevocationReferences = ValidationRequirementLevels.Must,
CadesCTimeStamp = ValidationRequirementLevels.Must,
CertificateValues = ValidationRequirementLevels.Must,
RevocationValues = ValidationRequirementLevels.Must
};
// Algoritmos permitidos
policy.DigestAlgorithms.Add(DigestAlgorithm.SHA256);
policy.SignatureAlgorithms.Add(SignatureAlgorithm.RSAWithSHA256);
// Informações da política
policy.PolicyOid = "";
policy.PolicyUri = new Uri("");
policy.PolicyDigest = new DigestAlgorithmAndValue(DigestAlgorithm.SHA256, adrcSha256Digest);
// Árbitro para validação dos certificados
policy.AddTrustArbitrator(TrustArbitrators.PkiBrazil);
// Período válido para assinatura
policy.SigningPeriodStart = GetBrasilia0H(2012, 3, 6);
policy.SigningPeriodEnd = GetBrasilia24H(2023, 6, 21);
// Política do carimbo de tempo de assinatura
policy.SignatureTimestampPolicy = new CadesPolicySpec() {
AttributeGeneration = new AttributeGenerationSpec() {
IncludeCompleteCertificateReferences = true,
IncludeCompleteRevocationReferences = true,
IncludeCertificateValues = true,
IncludeRevocationValues = true
},
AttributeValidation = new AttributeValidationSpec() {
SignaturePolicyIdentifier = ValidationRequirementLevels.MustNot,
SigningTime = ValidationRequirementLevels.MustNot,
SignatureTimestamp = ValidationRequirementLevels.MustNot,
CompleteCertificateReferences = ValidationRequirementLevels.Must,
CompleteRevocationReferences = ValidationRequirementLevels.Must,
CadesCTimeStamp = ValidationRequirementLevels.MustNot,
CertificateValues = ValidationRequirementLevels.Must,
RevocationValues = ValidationRequirementLevels.Must,
ArchiveTimestamp = ValidationRequirementLevels.MustNot
},
};
policy.SignatureTimestampPolicy.DigestAlgorithms.Add(DigestAlgorithm.SHA256);
policy.SignatureTimestampPolicy.SignatureAlgorithms.Add(SignatureAlgorithm.RSAWithSHA256);
policy.SignatureTimestampPolicy.AddTrustArbitrator(TrustArbitrators.PkiBrazil);
// Política do carimbo de tempo de referências
policy.CadesCTimestampPolicy = new CadesPolicySpec() {
AttributeGeneration = new AttributeGenerationSpec() {
IncludeCompleteCertificateReferences = true,
IncludeCompleteRevocationReferences = true,
IncludeCertificateValues = true,
IncludeRevocationValues = true
},
AttributeValidation = new AttributeValidationSpec() {
SignaturePolicyIdentifier = ValidationRequirementLevels.MustNot,
SigningTime = ValidationRequirementLevels.MustNot,
SignatureTimestamp = ValidationRequirementLevels.MustNot,
CompleteCertificateReferences = ValidationRequirementLevels.Must,
CompleteRevocationReferences = ValidationRequirementLevels.Must,
CadesCTimeStamp = ValidationRequirementLevels.MustNot,
CertificateValues = ValidationRequirementLevels.Must,
RevocationValues = ValidationRequirementLevels.Must,
ArchiveTimestamp = ValidationRequirementLevels.MustNot
},
};
policy.CadesCTimestampPolicy.DigestAlgorithms.Add(DigestAlgorithm.SHA256);
policy.CadesCTimestampPolicy.SignatureAlgorithms.Add(SignatureAlgorithm.RSAWithSHA256);
policy.CadesCTimestampPolicy.AddTrustArbitrator(TrustArbitrators.PkiBrazil);
return policy;
```
# [Políticas com grace period](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/grace-period)
Nos processos de validação existe um atraso inevitável entre a requisição de um estado de revogação e a distribuição de
um reporte desta revogação correspondente. Para garantir maior confiabilidade na validação de assinaturas é indicado o
uso de um **grace period**.
## Grace period
Grace period é o período de tempo previsto para que a informação de revogação de um certificado se propague pelo
processo interno da certificadora e chegue até sua publicação pelas partes confiáveis.
Na visão do verificador, é o menor intervalo de tempo que deve aguardar para requisitar o estado de revogação de um
certificado de modo que o emissor responsável tenha realizado esta publicação confiável.
Na validação de uma assinatura com data confiável, mecanismo que resiste a validade da assinatura mesmo após revogação
ou expiração do certificado do signatário, o verificador, antes de assertar sobre a validade deste certificado, deve
garantir que exista o grace period entre a referência de data da assinatura e a data dos dados da revogação utilizados.
Esta referência de data da assinatura pode ser um carimbo de tempo (TST) de assinatura ou uma marca de tempo
(time-mark) de uma autoridade confiável.
A informação do valor temporal do grace period, bem como a exigência ou não de que os valores de revogação a serem
utilizados tenham sido emitidos após o término deste grace period, são de responsabilidade da Autoridade Certificadora
e devem estar presentes na Declaração de Práticas de Certificação (DPC).

## Assinando com grace period
Só é possível criar assinaturas em que a política possui grace period utilizando a estratégia de finalização assíncrona
de assinatura. Para mais informações veja o artigo [Finalização assíncrona de assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades/finisher).
Para incluir um valor de tempo fixo para o grace period na política utilize o método
@Lacuna.Pki.Cades.CadesPolicySpec.SetGracePeriod(System.TimeSpan) como no exemplo abaixo.
```cs
// Carregando uma política de assinatura
CadesPolicySpec policy = ...
// Definindo grace period fixo de 2 horas
policy.SetGracePeriod(TimeSpan.FromHours(2));
```
Também é possível utilizar um @Lacuna.Pki.IGracePeriodCatalog, um catálogo de grace period que retorna o valor
temporal baseado em informações do certificado do signatário como Issuer ou CertificatePolicies e defini-lo na política
através da propriedade @Lacuna.Pki.Cades.CadesPolicySpec.GracePeriodCatalog.
```cs
// Implementação de um catálogo de grace period
IGracePeriodCatalog catalog = ...
policy.GracePeriodCatalog = catalog;
```
## Veja também
* [Finalização assíncrona de assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades/finisher)
* @Lacuna.Pki.Cades.BrazilCadesPolicySpec
# [Mapeadores de política (Policy mappers)](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/mappers)
Os mapeadores de políticas são utilizados para selecionar um tipo de política baseado em características do signatário.
O PKI SDK possui duas implementações de mapeadores, baseado em certificado e em SignerInfo. Caso sua aplicação
utilize diferentes políticas customizadas e seja necessário um mapeador para diferentes signatários, basta implementar
as interfaces @Lacuna.Pki.Cades.ICadesPolicyMapper ou @Lacuna.Pki.Cades.ICadesPolicyMapperBySignerInfo.
## Mapper por certificado
O mapper baseado em certificado pode ser usado tanto no momento da assinatura quanto na validação. É indicado em
cenários que políticas de assinaturas distintas, de versões ou padrões diferentes podem ser selecionadas através de
campos do certificado do signatário como emissor, política de certificação, entre outros.
O exemplo abaixo demonstra o uso de uma implementação da interface @Lacuna.Pki.Cades.ICadesPolicyMapper que seleciona,
baseado no certificado do signatário, uma versão da política de assinatura ICP-Brasil AD-RC entre as versões 1.0, 1.1,
2.0 e 2.1 configuradas na classe @Lacuna.Pki.Cades.BrazilCadesPolicySpec.
```cs
// Mapper ICP-Brasil com versões da política AD-RC
var mapper = BrazilCadesPolicyMappers.GetAdrCompleta();
var signer = new CadesSigner();
signer.SetSigningCertificate(signingCert);
signer.SetDataToSign(toSign);
signer.SetPolicy(mapper); // Define o mapper como política
signer.ComputeSignature();
```
Para mais informações sobre política ICP-Brasil no SDK veja o artigo [Políticas ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/pki-brazil).
## Mapper por SignerInfo
O mapper baseado em SignerInfo é utilizado na validação de assinaturas que têm política explícita, a política faz
parte dos atributos assinados, ou que possuem alguma outra característica específica nas informações do signatário
que seja possível recuperar a política de assinatura utilizada.
No exemplo abaixo demonstramos a validação de uma assinatura ICP-Brasil, que possui política explícita, utilizando
o mapper ICP-Brasil por SignerInfo. O mapper por signerInfo irá selecionar automaticamente a política de assinatura
com suas regras de validação através de informações para cada signatário dentro da assinatura.
```cs
byte[] cadesSigContent = ...
// Carregando assinatura
var cadesSig = CadesSignature.Open(cadesSigContent);
// Validação com Mapper por SignerInfo
var vrs = cadesSig.ValidateAllSignatures(BrazilCadesPolicyMappers.GetCadesSignerInfoPolicyMapper());
```
## Veja também
* [Políticas ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/pki-brazil)
* @Lacuna.Pki.Cades.ICadesPolicyMapper
* @Lacuna.Pki.Cades.ICadesPolicyMapperBySignerInfo
* @Lacuna.Pki.Cades.BrazilCadesPolicyMappers
* @Lacuna.Pki.Cades.BrazilCadesPolicySpec
# [Políticas ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/pki-brazil)
A Infraestrutura de Chaves Públicas Brasileira (ICP-Brasil) é uma cadeia hierárquica e de confiança que regula a emissão
de certificados digitais no Brasil. Também estabelece requisitos e normas para políticas de assinaturas digitais
descritas no documento
[DOC-ICP-15.03](http://www.iti.gov.br/images/twiki/URL/pub/Certificacao/DocIcp/docs13082012/DOC-ICP-15.03_-_Versao_6.1_2.pdf)
que devem ser utilizadas de maneira explícita nas assinaturas.
O PKI SDK possui objetos de política de assinatura já configurados nas normas ICP-Brasil que podem ser facilmente
acessados através das classes:
* @Lacuna.Pki.Cades.BrazilCadesPolicySpec (Para assinaturas CAdES)
* @Lacuna.Pki.Pades.PadesPolicySpec.PkiBrazil (Para assinaturas PAdES)
```cs
var signer = new CadesSigner();
// Define política de assinatura AD-RT v2.1
signer.SetPolicy(BrazilCadesPolicySpec.GetAdrTempoV21());
```
Ou também através dos mapeadores de políticas @Lacuna.Pki.Cades.BrazilCadesPolicyMappers.
```cs
var signer = new CadesSigner();
signer.SetPolicy(BrazilCadesPolicyMappers.GetAdrTempo());
```
## Veja também
* [Ciclo de vida da assinatura AD-RT](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/adrt-lifecycle)
* [Mapeadores de política (Policy mappers)](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/mappers)
* @Lacuna.Pki.Cades.BrazilCadesPolicySpec
* @Lacuna.Pki.Cades.BrazilCadesPolicyMappers
# [Políticas com referências, mas sem valores](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/policies/revocation-refs-without-values)
Existem políticas de assinaturas que definem uso de carimbo de tempo sobre as referências de revogação, mas não incluem
os valores completos de revogação. Em outras palavras, a política diz que na assinatura deve haver um carimbo de tempo
sobre o digest dos dados de revogação utilizados para validar o certificado do signatário, mas não é obrigatório que
também seja guardado esses dados no pacote da assinatura, como pode ser observado nas políticas CAdES-C, CAdES-X
Type 1 ou ICP-Brasil AD-RV.
Para uma aplicação poder validar uma assinatura neste cenário, é preciso ter recursos para recuperar os dados exatos de
revogação referentes ao digest carimbado na assinatura. A solução no SDK deve utilizar um
@Lacuna.Pki.Stores.IReferencedCrlStore no momento da assinatura, assim, guardando os dados de revogação utilizados na
validação dos certificados indexados pelo seu digest, o que tornará a validação da assinatura eficiente e consistente.
O exemplo abaixo demonstra o uso desta solução.
```cs
// IReferencedCrlStore implementado
IReferencedCrlStore refStore = ...
// Criando assinatura
var signer = new CadesSigner();
signer.SetSigningCertificate(signingCert);
signer.SetDataToSign(toSign);
signer.SetPolicy(policy); // Política com referências mas sem valores
signer.SetCrlStore(refStore); // Definindo store para guardar valores referenciados na validação
signer.SetTimestampRequester(tsRequester);
signer.ComputeSignature();
byte[] sig = signer.GetSignature();
// Validando assinatura
var cadesSig = CadesSignature.Open(sig);
var vrs = cadesSig.ValidateAllSignatures(policy, crlStore: refStore);
```
Caso já utilize uma implementação de @Lacuna.Pki.Stores.ISimpleStore, por exemplo na
[compressão de assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades/compression), é possível instanciar um @Lacuna.Pki.Stores.IReferencedCrlStore
facilmente como demonstrado no exemplo abaixo através da implementação
@Lacuna.Pki.Stores.ReferencedCrlStoreFromSimpleStore.
```cs
// ISimpleStore já implementado
ISimpleStore simpleStore = ...
// Instanciando IReferencedCrlStore
IReferencedCrlStore refStore = new ReferencedCrlStoreFromSimpleStore(simpleStore);
```
> **Observação**
>
> Esta estratégia pode ser favorável caso os dados de revogação verificados sejam CRLs, que podem ser arquivos extensos
> chegando a alguns mega bytes de tamanho. Mas caso os dados de revogação sejam pequenos, como exemplo dos OCSPs, é
> mais indicado que seus valores completos sejam também incluídos no pacote de assinatura, o que tornará a validação um
> processo mais eficiente e menos complexo.
## Veja também
* [Compressão de assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades/compression)
* @Lacuna.Pki.Stores.IReferencedCrlStore
* @Lacuna.Pki.Stores.ISimpleStore
* @Lacuna.Pki.Stores.ReferencedCrlStoreFromSimpleStore
# [Assinatura com chave remota (assinatura no browser)](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/web-remote)
Pode ser necessário criar uma assinatura digital em situações em que a chave privada do signatário não está no mesmo ambiente
de execução do SDK, tornando necessária uma assinatura remota. Como exemplo, podemos descrever uma aplicação Web que cria
pacote de assinatura de documentos e requisita a assinatura a um usuário que possui sua chave privada em um smartcard ou
token conectado ao seu computador. A figura abaixo exemplifica o diagrama deste cenário.

A assinatura com chave remota pode ser feita em outras situações, por exemplo, em conjunto entre um servidor e uma aplicação
desktop que se comunica com o servidor via web service. Entretanto, o caso mais comum é realizá-la em aplicações web, fazendo
a parte *client-side* no browser. Para isso, utilize o componente [Web PKI](https://docs.lacunasoftware.com/articles/web-pki). As licenças de uso do
SDK geralmente acompanham licenças de uso para o componente. Se você ainda não possui uma licença de uso para o componente,
[solicite uma](https://www.lacunasoftware.com/pt/home/purchase).
O restante desse tópico trata da parte *server-side* da assinatura. Para detalhes sobre a parte *client-side*, veja a
[documentação do componente Web PKI](https://docs.lacunasoftware.com/articles/web-pki) ou veja o
[projeto de exemplo](https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/dotnet), que mostra o uso do SDK em conjunto com o Web PKI.
## Assinatura CAdES com chave remota
Abaixo temos um exemplo de uma assinatura CAdES com chave remota em que há representado dois momentos distintos no servidor,
antes e após a assinatura remota. O primeiro momento tem o objetivo de gerar os atributos assinados (bytes que serão
assinados pelo cliente), o segundo momento é responsável pela finalização com a assinatura pré-computada do cliente.
```cs
// Certificado do signatário (sem chave privada associada)
PKCertificate signingCert = ...
//-----------------------------------------------------------------------
// Servidor (Início da assinatura)
var signer = new CadesSigner();
signer.SetSigningCertificate(signingCert);
signer.SetDataToSign(documentBytes);
signer.SetPolicy(policy);
SignatureAlgorithm signatureAlg;
byte[] toSign = signer.GenerateToSignBytes(out signatureAlg); // Gera os bytes a serem assinados pelo cliente remotamente
// Enviar os bytes toSign e algoritmo de assinatura ao cliente
//-----------------------------------------------------------------------
// Assinatura no cliente
//-----------------------------------------------------------------------
// Servidor (Finalização da assinatura)
// Recebendo os bytes assinados
byte[] signature = ...
var signer = new CadesSigner();
signer.SetSigningCertificate(signingCert);
signer.SetDataToSign(documentBytes);
signer.SetPolicy(policy);
signer.SetPrecomputedSignature(signature, toSign); // Define a assinatura remota
signer.ComputeSignature();
byte[] cadesSig = signer.GetSignature();
// ----------------------------------------------------------------------
```
## Assinatura PAdES com chave remota
A assinatura PAdES é um objeto interno ao documento PDF assinado que também proteje sua integridade. Este objeto de
assinatura possui uma referência de tempo, portanto para não alterar parâmetros internos ao PDF, que invalidariam a
assinatura em respeito a integridade, é necessário armazenar uma estrutura de dados (*PadesTransferData*) temporariamente
entre as etapas de início e finalização.
Abaixo temos um exemplo de uma assinatura PAdES com chave remota em que há representado dois momentos distintos no servidor,
antes e após a assinatura remota:
```cs
// Certificado do signatário (sem chave privada associada)
PKCertificate signingCert = ...
//-----------------------------------------------------------------------
// Servidor (Início da assinatura)
var signer = new PadesSigner();
signer.SetPdfToSign(pdfBytes);
signer.SetSigningCertificate(signingCert);
signer.SetPolicy(policy);
SignatureAlgorithm sigAlgorithm;
byte[] transData;
// Retorna os bytes a serem assinados pelo cliente remotamente e os dados a serem armazenados entre as etapas de início/finalização
byte[] toSign = signer.GetToSignBytes(out sigAlgorithm, out transData);
// Armazenar a estrutura transData
// Enviar os bytes toSign e algoritmo de assinatura ao cliente
//-----------------------------------------------------------------------
// Assinatura no cliente
//-----------------------------------------------------------------------
// Servidor (Finalização da assinatura)
// Recebendo os bytes assinados
byte[] signature = ...
var signer = new PadesSigner();
signer.SetPreComputedSignature(signature, transData); // Define a assinatura remota
signer.SetPolicy(policy);
signer.ComputeSignature();
byte[] signedPdf = signer.GetPadesSignature();
//-----------------------------------------------------------------------
```
## Veja também
* [Assinaturas padrão CAdES](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades)
* [Assinaturas padrão PAdES](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades)
* [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
# [Assinaturas padrão XML](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/xml)
O PKI SDK suporta os padrões de assinatura XmlDSig e XAdES. A assinatura em XML pode ser realizada sobre um documento
completo, sobre um elemento específico interno ao documento ou sobre um conteúdo externo. Para exemplos básicos, as
seções abaixo demonstram o assinador realizando assinaturas sobre um documento XML completo e sobre um elemento
específico de um XML.
## Assinatura de um documento XML
O código abaixo exemplifica uma assinatura XAdES básica de um documento XML completo.
```cs
// política de assinatura XAdES ICP-Brasil AD-RB
var policy = BrazilXmlPolicySpec.GetAdrBasicaV22(true);
// Instanciamos o objeto signer que é responsável por criar a assinatura
var signer = new FullXmlSigner();
signer.SetSigningCertificate(cert); // certificado do signatário com chave privada associada
signer.SetXml(xmlDocument); // bytes ou objeto XmlDocument do documento XML a ser assinado
signer.SetPolicy(policy); // Política de assinatura AD-RB
signer.ComputeSignature(); // Cria elemento de assinatura
var signedXml = signer.GetSignedXml(); // Retorna os bytes do documento XML assinado
```
Neste modo de assinatura, por padrão, a transformação do elemento assinado será enveloped e o elemento de assinatura
resultante será incluído como filho do elemento raiz no documento finalizado. Para mais informações sobre a inclusão
do elemento de assinatura e modos de assinatura XML, veja o artigo [Modos de inclusão](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/xml/insertion).
## Assinatura de um elemento em um documento XML
O código abaixo exemplifica uma assinatura XAdES básica de um elemento específico pertencente a um documento XML.
> **Observação**
>
> Para assinatura de um elemento do XML é necessário que o elemento tenha um atributo ID único no XML.
```cs
// política de assinatura XAdES ICP-Brasil AD-RB
var policy = BrazilXmlPolicySpec.GetAdrBasicaV22(true);
// Instanciamos o objeto signer que é responsável por criar a assinatura
var signer = new XmlElementSigner();
signer.SetSigningCertificate(cert); // certificado do signatário com chave privada associada
signer.SetXml(xmlDocument); // bytes ou objeto XmlDocument do documento XML que contém o elemento a ser assinado
signer.SetToSignElementId(id); // define o ID do elemento que será assinado
signer.SetPolicy(policy);
signer.ComputeSignature(); // Cria elemento de assinatura
var signedXml = signer.GetSignedXml(); // Retorna os bytes do documento XML contendo o elemento de assinatura
```
Neste modo de assinatura, por padrão, é criada uma assinatura *detached* em que o elemento de assinatura é incluído
como irmão do elemento assinado. Para mais informações sobre a inclusão do elemento de assinatura e modos de assinatura
XML, veja o artigo [Modos de inclusão](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/xml/insertion).
## Assinatura de Nota Fiscal eletrônica
O código abaixo exemplifica uma assinatura de Nota Fiscal eletrônica (NFe) no padrão nacional:
```cs
// política de assinatura para NFe
var policy = BrazilXmlPolicySpec.GetNFePadraoNacional();
// Instanciamos o objeto signer que é responsável por criar a assinatura
var signer = new XmlElementSigner();
signer.SetSigningCertificate(cert); // certificado do signatário com chave privada associada
signer.SetXml(xmlNFe); // bytes ou objeto XmlDocument da NFe a ser assinada
signer.SetToSignElementId("NFe0000001"); // define o ID da NFe a ser assinado
signer.SetPolicy(policy);
signer.ComputeSignature(); // Cria elemento de assinatura
var signedNFe = signer.GetSignedXml(); // Retorna os bytes da NFe assinada
```
## Veja também
* @Lacuna.Pki.Xml.XmlElementSigner
* @Lacuna.Pki.Xml.FullXmlSigner
**Demonstrações e exemplos**
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Validate a signed PDF using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenPadesSignatureSdkController.cs)
- Validate a CMS/CAdES signature using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenCadesSignatureSdkController.cs)
- Validate a signed XML using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/OpenXmlSignatureSdkController.cs)
- Sign an XML document using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlSignatureSdkController.cs)
- Sign an XML document with a server key using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlServerKeySdkController.cs)
- Sign a Brazilian NF-e using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlNFeSignatureSdkController.cs)
- Sign a COD XML using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/XmlCodSignatureSdkController.cs)
# [Assinatura de conteúdo externo](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/xml/detached-resources)
O padrão de assinatura XML permite a criação de assinaturas cujo conteúdo a ser assinado é externo ao documento XML ou
assinatura de conteúdos quaisquer.
O PKI SDK oferece duas maneiras de realizar esta assinatura de conteúdo externo. O assinador
@Lacuna.Pki.Xml.DetachedResourceXmlSigner destina-se a assinaturas de conteúdos locais à aplicação. O assinador
@Lacuna.Pki.Xml.OnlineResourceXmlSigner destina-se a assinatura de conteúdos online, externos à aplicação.
Nesta modalidade de assinatura, há a possibilidade de não passar nenhum documento XML para o assinador. Assim, o
resultado retornado `signedXml` será um novo documento XML contento o nó de assinatura como nó raiz.
Caso seja passado um documento XML para o assinador (`signer.SetXml(xmlDocument)`), o nó de assinatura será incluído
no XML passado, seguindo o padrão de inclusão descrito no artigo [Modos de inclusão](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/xml/insertion).
Veja abaixo os exemplos de cada assinador:
## Conteúdo externo local
```cs
var signer = new DetachedResourceXmlSigner();
signer.SetToSignDetachedResource(resource, "uri.to.resource");
signer.SetPolicy(policy);
signer.SetSigningCertificate(certWithKey);
signer.ComputeSignature();
var signedXml = signer.GetSignedXml();
```
## Conteúdo externo online
```cs
var signer = new OnlineResourceXmlSigner();
signer.SetToSignResourceUri(resourceUri);
signer.SetPolicy(policy);
signer.SetSigningCertificate(certWithKey);
signer.ComputeSignature();
var signedXml = signer.GetSignedXml();
```
# [Resolução de IDs](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/xml/id-resolution)
Para assinatura de um elemento do XML, é necessário que este elemento possua um atributo ID único no documento. Caso o
XML defina um nome diferente para o atributo ID, será necessário a definição de uma resolução de ID.
A resolução de ID pode ser definida tanto globalmente, caso a mudança no atributo ID seja aplicada no XML inteiro,
quanto definida localmente, caso a mudança no atributo ID seja aplicado apenas em um elemento.
Para os exemplos abaixo, iremos assinar o seguinte XML, que possui o atributo Id alterado para `licenseId`.
```xml
www.contoso.com
www.contoso.com
2016-03-23
```
## Definindo uma tabela de resolução de ID's global
Caso a alteração seja válida globalmente, o código para assinatura deve ser:
```cs
// Create ID resolution table for whole XML
var idResolutionTable = new XmlIdResolutionTable();
idResolutionTable.AddGlobalIdAttribute("licenseId");
var signer = new XmlElementSigner();
signer.SetXml(xml);
signer.SetSigningCertificate(cert);
signer.SetPolicy(policy);
signer.SetToSignElementId(":9e2e5bbc-b4a0-41dc-a342-98f1554f32a3");
signer.SetIdResolutionTable(idResolutionTable); // set ID resolution table
signer.ComputeSignature();
```
> **Observação**
>
> Caso o atributo possua um namespace específico, é possível passá-lo através do mesmo método
> @Lacuna.Pki.Xml.XmlIdResolutionTable.AddGlobalIdAttribute(System.String,System.String)
## Definindo uma tabela de resolução de ID's local
Para este exemplo, iremos definir que a alteração do ID deve ser aplicada apenas no elemento ``.
```cs
// Create ID resolution table for element name and namespace
var idResolutionTable = new XmlIdResolutionTable();
idResolutionTable.SetElementIdAttribute("licenseInfo", "http://www.lacunasoftware.com/", "licenseId");
var signer = new XmlElementSigner();
signer.SetXml(xml);
signer.SetSigningCertificate(cert);
signer.SetPolicy(policy);
signer.SetToSignElementId(":9e2e5bbc-b4a0-41dc-a342-98f1554f32a3");
signer.SetIdResolutionTable(idResolutionTable); // set ID resolution table
signer.ComputeSignature();
```
> **Observação**
>
> Caso o atributo possua um namespace específico, é possível passá-lo através do mesmo método
> @Lacuna.Pki.Xml.XmlIdResolutionTable.SetElementIdAttribute(System.String,System.String,System.String,System.String)
## Veja também
* @Lacuna.Pki.Xml.XmlIdResolutionTable
* @Lacuna.Pki.Xml.XmlElementSigner
# [Modos de inclusão](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/xml/insertion)
O processo de assinatura de um documento XML produz um elemento `` contendo as informações daquela
assinatura. Este elemento é incluído no XML seguindo o padrão descrito na tabela abaixo. Também é possível
determinar um local de inclusão customizado, como exemplifica a seção
[Definindo local para inclusão do elemento de assinatura](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/xml/insertion#custom-location).
## Padrão de inclusão do nó de assinatura
Assinador | Padrão | Padrão sem um documento XML passado
----------------------------------------- | -------------------------- | -----------------------------------
@Lacuna.Pki.Xml.FullXmlSigner | Filho do elemento raiz | (Não se aplica)
@Lacuna.Pki.Xml.XmlElementSigner | Irmão do elemento assinado | (Não se aplica)
@Lacuna.Pki.Xml.OnlineResourceXmlSigner | Filho do elemento raiz | Elemento raiz
@Lacuna.Pki.Xml.DetachedResourceXmlSigner | Filho do elemento raiz | Elemento raiz
> **Observação**
>
> Nos assinadores @Lacuna.Pki.Xml.FullXmlSigner e @Lacuna.Pki.Xml.XmlElementSigner é obrigatório passar um documento
> XML, portanto não se aplica a coluna *Padrão sem um documento XML passado*.
Caso se queira definir um local customizado para a inclusão do elemento de assinatura, verifique a seção seguinte.
## Definindo local para inclusão do elemento de assinatura
No PKI SDK, é possível customizar o local de inclusão do elemento de assinatura através do método
@Lacuna.Pki.Xml.XmlSigner.SetSignatureElementLocation(System.String,Lacuna.Pki.Xml.NamespaceManager,Lacuna.Pki.Xml.XmlInsertionOptions)
nos assinadores.
Como exemplo, iremos fazer uma assinatura do documento XML inteiro abaixo:
```xml
Lacuna Software Ltda EPP
20658903000171
Banco do Brasil SA
00000000000191
-
10201
Philips screws
100
```
Queremos, então, assinar o documento acima e incluir o elemento de assinatura dentro do elemento ``.
O código abaixo demonstra como fazê-lo.
```cs
// Create a namespace manager with the necessary namespaces to
// get to the target element for the signature insertion
var nsm = new NamespaceManager();
nsm.AddNamespace("ls", "http://www.lacunasoftware.com/sample");
string xpath = "//ls:signaturePlaceholder"; // XPath to the target element
var signer = new FullXmlSigner();
signer.SetXml(lacunaInvoiceXmlBytes); // Set the XML document bytes
signer.SetPolicy(policy); // Set the signature policy
signer.SetSigningCertificate(cert); // Set the certificate with key
// Set the signature element location
signer.SetSignatureElementLocation(xpath, nsm, XmlInsertionOptions.AppendChild);
signer.ComputeSignature();
var signedXml = signer.GetSignedXml(); // Return signed XML
```
O resultado do XML assinado terá o elemento de assinatura inserido como filho do elemento ``.
```xml
...
...
```
> **Observação**
>
> Em relação ao parâmetro `string xpath` do método
> @Lacuna.Pki.Xml.XmlSigner.SetSignatureElementLocation(System.String,Lacuna.Pki.Xml.NamespaceManager,Lacuna.Pki.Xml.XmlInsertionOptions)
> no assinador @Lacuna.Pki.Xml.XmlElementSigner, o xpath é executado a partir do elemento escolhido para assinatura,
> enquanto nos outros assinadores é executado a partir do elemento raiz do XML.
# [Pacotes nuget opcionais](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages)
O PKI SDK é distribuído via Nuget Package Manager. Além do pacote principal,
[Lacuna.Pki](https://www.nuget.org/packages/Lacuna.Pki/), existem alguns pacotes opcionais que habilitam integrações
com provedores e outras bibliotecas.

As setas no diagrama acima representam dependências. O pacote **Lacuna.Pki** não possui dependências e cada pacote
opcional depende dele e de um outro pacote, com o qual ele habilita integração.
## Integração para armazenamento
Algumas operações requerem um "storage externo", um local para armazenar e recuperar dados. Alguns exemplos de
operações que requerem um storage externo são:
* Compressão e descompressão de assinaturas CAdES (para mais informações veja [Compressão de assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/cades/compression)).
* Criação e validação de assinaturas CAdES com referências de validação porém sem valores de validação (por exemplo, políticas CAdES-C, CAdES-X Type 1 e ICP-Brasil AD-RV).
Para realizar essa operações, é necessário passar para o SDK um objeto que implemente a interface
`Lacuna.Pki.Stores.ISimpleStore`. Já existe no pacote principal uma implementação da interface, a classe
@Lacuna.Pki.Stores.FileSystemSimpleStore, que salva e recupera os arquivos no sistema de arquivos local. Entretanto,
para sistemas em larga escala, o armazenamento em disco pode não ser suficiente no que se refere à confiabilidade.
Os seguintes pacotes opcionais oferecem outras possibilidade de armazenamento:
* [Azure Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/azure-connector) (armazenamento no Azure Blob Storage)
* [Amazon Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/amazon-connector) (armazenamento no Amazon S3)
* [Entity Framework Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/ef-connector) (armazenamento em banco de dados)
## Integração para log
Os seguintes pacotes oferecem a possibilidade de logar mensagens internas do SDK para diagnóstico:
* [NLog Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/nlog-connector) (enviar logs para a biblioteca NLog)
* [Entity Framework Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/ef-connector) (enviar logs para o banco de dados)
* [Azure Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/azure-connector) (enviar logs para o Azure Table Storage)
O pacote recomendado para log é o [NLog Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/nlog-connector), pois através do **NLog** é possível enviar logs
para diferentes locais, inclusive para o Azure e para o banco de dados, aproveitando os benefícios de performance dessa
biblioteca específica para tal propósito.
## Integração para armazenamento de nonces
Para realizar autenticação com certificados digitais, o SDK requer um storage externo para armazenar *nonces*
criptográficos. O pacote [Entity Framework Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/ef-connector) oferece uma forma de armazenar tais *nonces* no
banco de dados utilizando o Microsoft Entity Framework.
# [Amazon Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/amazon-connector)
O pacote opcional [Lacuna PKI Amazon Connector](https://www.nuget.org/packages/Lacuna.Pki.AmazonConnector/) possibilita
as seguintes integrações entre o SDK e o Amazon Web Services (AWS):
* Compressão e descompressão de assinaturas CAdES armazenando as LCRs e certificados em um bucket do Amazon S3
* Criação e validação de assinaturas CAdES com referência de revogação porém sem valores de revogação armazenando os valores em um bucket do Amazon S3
## Credenciais do AWS
A primeira coisa que você precisará é criar uma conta no AWS S3 e obter credenciais de acesso. Se você ainda não tiver
feito isso, os links abaixo podem auxiliá-lo nesse processo:
* [Getting Your Access Key ID and Secret Access Key](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html)
* [Managing Access Keys for your AWS Account](http://docs.aws.amazon.com/general/latest/gr/managing-aws-access-keys.html)
Ao final desse processo, você deve ter os seguintes parâmetros:
* Access Key ID
* Secret Access Key
* Bucket name
* Bucket region
## Utilizando o armazenamento no Amazon S3
A classe @Lacuna.Pki.AmazonConnector.AmazonS3Store, incluída no pacote opcional, implementa a interface
@Lacuna.Pki.Stores.ISimpleStore, utilizada pelo SDK quando um storage externo é necessário (para mais informações, veja
[Pacotes nuget opcionais](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages)).
Para usá-la, basta instanciá-la com os parâmetros mencionados na seção anterior:
```cs
var store = AmazonS3Store.CreateFromAccessCredentials(accessKeyId, awsSecretAccessKey, RegionEndpoint.USEast1, bucketName);
```
Você pode opcionalmente especificar um basePath, uma pasta dentro do bucket na qual armazenar os objetos:
```cs
var store = AmazonS3Store.CreateFromAccessCredentials(accessKeyId, awsSecretAccessKey, RegionEndpoint.USEast1, bucketName, "lacuna-pki-store");
```
Isso adicionaria o prefixo `lacuna-pki-store/` a todas as chaves de objetos. Se não for especificado um `basePath`, os
objetos serão criados na raiz do bucket.
Tendo uma instância da classe `AmazonS3Store` associada à sua conta do Amazon S3, você pode utilizá-la em qualquer
lugar que o SDK exija um `ISimpleStore`, por exemplo para comprimir e descomprimir assinaturas CAdES:
```cs
byte[] precomputedSignature = ...; // qualquer assinatura CAdES, não necessariamente feita com o SDK
var compressedSignature = CadesSignatureCompression.Compress(precomputedSignature, store);
var decompressedSignature = CadesSignatureCompression.Decompress(compressedSignature, store);
```
## Dependência do pacote AWS SDK for .NET
Para maximizar a compatibilidade, o pacote Lacuna PKI Amazon Connector depende de uma versão antiga do pacote
**AWSSDK**. Entretanto, recomendamos fortemente que seja utilizado versão mais atual do pacote.
## Código-fonte
O código-fonte do pacote Lacuna PKI Amazon Connector é aberto, ficando hospedado no
[BitBucket](https://bitbucket.org/Lacunas/pkiamazonconnector). Caso queira customizá-lo, você pode fazer um fork do
projeto e utilizar a sua versão customizada ao invés do pacote opcional.
# [Azure Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/azure-connector)
O pacote [Lacuna PKI Azure Connector](https://www.nuget.org/packages/Lacuna.Pki.AzureConnector/) possibilita as seguintes integrações entre o SDK e o [Microsoft Azure Storage Service](https://azure.microsoft.com/pt-br/services/storage/):
* Utilizar chaves armazenadas em um [Azure Key Vault](https://azure.microsoft.com/pt-br/services/key-vault/)
* Compressão e descompressão de assinaturas CAdES armazenando LCRs e certificados em um [Blob Storage](https://docs.microsoft.com/pt-br/azure/storage/storage-dotnet-how-to-use-blobs)
* Criação e validação de assinaturas CAdES com referência de revogação porém sem valores de revogação armazenando os valores em um Blob Storage
* Enviar mensagens de log geradas pelo SDK para um [Table Storage](https://docs.microsoft.com/pt-br/azure/storage/storage-dotnet-how-to-use-tables)
> **Observação**
>
> Para informações sobre integração com Azure Key Vault, [clique aqui](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/azure-connector/key-vault)
## Credenciais do Azure Storage
A primeira coisa que você vai precisar é criar uma conta no Azure Storage e obter credenciais de acesso, denominadas *Access Keys* no vocabulário do Azure. Se você ainda não fez isso, siga as instruções contidas [neste link](https://azure.microsoft.com/pt-br/documentation/articles/storage-create-storage-account/).
Ao final desse processo você deve ter uma *connection string* similar a:
DefaultEndpointsProtocol=https;AccountName=youraccount;AccountKey=XXXXXXXXXX==
O lugar ideal para colocar essa string é no seu `web.config` ou `app.config`:
```xml
```
## Utilizando o armazenamento em Blob Storage
A classe @Lacuna.Pki.AzureConnector.AzureBlobStorageStore, incluída no pacote opcional, implementa a interface @Lacuna.Pki.Stores.ISimpleStore, utilizada pelo SDK quando um storage externo é necessário (para mais informações, veja [Pacotes nuget opcionais](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/azure-connector)).
Se você armazenou sua *connection string* do Azure Storage no `web.config` como sugerido acima, você deve instanciar a classe @Lacuna.Pki.AzureConnector.AzureBlobStorageStore da seguinte maneira:
```csharp
var store = AzureBlobStorageStore.CreateFromSettingName("AzureStorage"); // ou o que você colocou no atributo "key" da tag appSetting
```
Também é possível deixar a *connection string* no código:
```csharp
var store = AzureBlobStorageStore.CreateFromConnectionString("DefaultEndpointsProtocol=https;AccountName=youraccount;AccountKey=XXXXXXXXXX==");
```
Os objetos são armazenados por padrão em um container denominado "lacuna-pki-store", mas é possível alterar isso:
```csharp
var store = AzureBlobStorageStore.CreateFromSettingName("AzureStorage", "my-container");
```
Tendo uma instância da classe @Lacuna.Pki.AzureConnector.AzureBlobStorageStore associada à sua conta do Azure Storage, você pode utilizá-la em qualquer lugar que o SDK exija um @Lacuna.Pki.Stores.ISimpleStore, por exemplo para comprimir e descomprimir assinaturas CAdES:
```csharp
byte[] precomputedSignature = ...; // qualquer assinatura CAdES, não necessariamente feita com o SDK
var compressedSignature = CadesSignatureCompression.Compress(precomputedSignature, store);
var decompressedSignature = CadesSignatureCompression.Decompress(compressedSignature, store);
```
## Enviando logs para um Table Storage
A classe @Lacuna.Pki.AzureConnector.AzureTableStorageLogger é utilizada para enviar logs gerados pelo SDK para uma tabela do Azure Table Storage. Instancia-se a classe da mesma maneira que a @Lacuna.Pki.AzureConnector.AzureBlobStorageStore:
```csharp
var logger = AzureTableStorageLogger.CreateFromSettingName("AzureStorage"); // ou o que você colocou no atributo "key" da tag appSetting
```
Os logs são armazenados por padrão em uma tabela denominada "LacunaPkiLog", mas é possível alterar essa configuração:
```csharp
var logger = AzureTableStorageLogger.CreateFromSettingName("AzureStorage", "MyTable");
```
Também é possível especificar um nível mínimo de log a ser considerado (o padrão é logar de `Info` para cima):
```csharp
var logger = AzureTableStorageLogger.CreateFromSettingName("AzureStorage", minLevel: LogLevels.Trace); // this would log A LOT, use only for diagnostics
```
Tendo uma instância da classe @Lacuna.Pki.AzureConnector.AzureTableStorageLogger, basta então chamar o método @Lacuna.Pki.AzureConnector.AzureTableStorageLogger.Configure para configurar o SDK para enviar os logs para o Azure:
```csharp
logger.Configure();
```
Geralmente faz-se apenas o seguinte:
```csharp
AzureTableStorageLogger.CreateFromSettingName("AzureStorage").Configure();
```
## Código-fonte
O código-fonte do pacote **Lacuna PKI Azure Connector** é aberto, ficando hospedado no [BitBucket](https://bitbucket.org/Lacunas/pkiazureconnector). Caso queira customizá-lo, você pode fazer um fork do projeto e utilizar a sua versão customizada ao invés do pacote opcional.
# [Integração com Azure Key Vault](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/azure-connector/key-vault)
O pacote [Lacuna PKI Azure Connector](https://www.nuget.org/packages/Lacuna.Pki.AzureConnector/) possibilita utilizar certificados e chaves armazenadas em
[Azure Key Vaults](https://azure.microsoft.com/pt-br/services/key-vault/) para assinar documentos, emitir certificados etc.
Para utilizar certificados ou chaves armazenados em um key vault, você precisará dos seguintes parâmetros:
* **Endpoint**: campo `DnsName` do key vault, pode ser obtido na área *Overview* do vault
* **AppId**: campo **Application ID** de uma aplicação registrada no *Microsoft Entra ID*
* **AppSecret**: segredo da aplicação, gerado na área *Certificates & secrets* da aplicação
Além disso, para cada certificado ou chave que se pretende utilizar é necessário saber seu ***name***.
## Instruções para uso
> **Dica**
>
> Veja a sessão [ASP.NET Core](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/azure-connector/key-vault#aspnet-core) abaixo caso a sua aplicação utilize essa tecnologia.
De posse dos parâmetros necessários, instancie um @Lacuna.Pki.AzureConnector.AzureKeyVaultOptions e, com este, um @Lacuna.Pki.AzureConnector.AzureApiAuthenticator:
```cs
var options = new AzureKeyVaultOptions() {
Endpoint = "...",
AppId = "...",
AppSecret = "...",
};
var azureApiAuthenticator = new AzureApiAuthenticator(options);
```
> **Observação**
>
> A sua aplicação deve manter uma única instância do `AzureApiAuthenticator` (*singleton lifetime*).
Em seguida, instancie um @Lacuna.Pki.AzureConnector.AzureCertificateProvider
e então solicite um @Lacuna.Pki.PKCertificateWithKey passando o `certificateName` do certificado que deseja utilizar:
> **Observação**
>
> Se você armazena apenas as chaves dos certificados no Azure Key Vault, mas não os próprios certificados, leia a [seção abaixo](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/azure-connector/key-vault#external-cert).
```cs
var azureCertProvider = new AzureCertificateProvider(options, azureApiAuthenticator);
var certWithKey = await azureCertProvider.GetCertificateWithKeyAsync(/* certificateName */);
```
> **Observação**
>
> A instância do `AzureCertificateProvider` deve ser criada e descartada à medida que for necessário (*transient lifetime*).
Com um `PKCertificateWithKey`, é possível fazer assinaturas em "um único passo", por exemplo:
```cs
var signer = new PadesSigner();
signer.SetPdfToSign(/* PDF file path, content or stream */);
signer.SetSigningCertificate(certWithKey);
signer.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(TrustArbitrators.PkiBrazil));
signer.ComputeSignature();
var signedPdf = signer.GetPadesSignature();
```
Segue um exemplo completo:
```cs
// System-wide singletons
Global.AzureKeyVaultOptions = new AzureKeyVaultOptions() {
Endpoint = "...",
AppId = "...",
AppSecret = "...",
};
Global.AzureApiAuthenticator = new AzureApiAuthenticator(options);
...
var azureCertProvider = new AzureCertificateProvider(Global.AzureKeyVaultOptions, Global.AzureApiAuthenticator);
var certWithKey = await azureCertProvider.GetCertificateWithKeyAsync(/* certificateName */);
var signer = new PadesSigner();
signer.SetPdfToSign(/* PDF file path, content or stream */);
signer.SetSigningCertificate(certWithKey);
signer.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(TrustArbitrators.PkiBrazil));
signer.ComputeSignature();
var signedPdf = signer.GetPadesSignature();
```
## ASP.NET Core
Caso a sua aplicação web seja em ASP.NET Core, adicione o seguinte código ao método `ConfigureServices` do *startup* da sua aplicação:
```cs
public void ConfigureServices(IServiceCollection services) {
...
services.AddAzureKeys()
.Configure(Configuration.GetSection("AzureKeyVault"));
}
```
No arquivo de configuração *appsettings.json*, adicione a seção `AzureKeyVault`:
```json
...
"AzureKeyVault": {
"Endpoint": "https://my-key-vault.vault.azure.net/",
"AppId": "...",
"AppSecret": "..."
},
...
```
Nas partes da aplicação que precisarem utilizar certificados armazenados no Key Vault, peça via *dependency injection* uma instância de
@Lacuna.Pki.AzureConnector.IAzureCertificateProvider:
```cs
using Lacuna.Pki.AzureConnector;
public MyController : ApiController {
private readonly IAzureCertificateProvider azureCertProvider;
public MyController(IAzureCertificateProvider azureCertProvider) {
this.azureCertProvider = azureCertProvider;
}
...
}
```
Daí em diante, o uso é idêntico ao descrito na seção anterior:
```cs
var certWithKey = await azureCertProvider.GetCertificateWithKeyAsync(/* certificateName */);
var signer = new PadesSigner();
signer.SetPdfToSign(/* PDF file path, content or stream */);
signer.SetSigningCertificate(certWithKey);
signer.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(TrustArbitrators.PkiBrazil));
signer.ComputeSignature();
var signedPdf = signer.GetPadesSignature();
```
## Usando certificados apenas com chave armazenada em Key Vault
Você pode optar por armazenar em Azure Key Vault apenas as chaves dos certificados, armazenando na sua aplicação a parte pública dos certificados (arquivos .cer/.crt/.pem)
correspondente às chaves.
Nesse caso, instancie um @Lacuna.Pki.AzureConnector.AzureKeyProvider e então solicite uma @Lacuna.Pki.AzureConnector.AzureKey passando o `keyName` da
chave que deseja utilizar:
```cs
var azureKeyProvider = new AzureKeyProvider(options, azureApiAuthenticator);
var key = await azureKeyProvider.GetKeyAsync(/* keyName */);
```
> **Observação**
>
> A instância do `AzureKeyProvider` deve ser criada e descartada à medida que for necessário (*transient lifetime*).
A classe `AzureKey` implementa a interface @Lacuna.Pki.IPrivateKey, portanto pode ser usada como tal:
```cs
byte[] toSignHash = ...;
var signature = key.GetSignatureCsp(DigestAlgorithm.SHA256).SignHash(toSignHash);
```
Entretanto, a forma mais fácil de usar a chave é chamando o método `GetCertificateWithKey()` com o certificado (que pode estar disponível como
uma configuração da sua aplicação, ou armazenado em banco de dados, ou ainda estar *hardcoded*):
```cs
var certificate = PKCertificate.Decode(/* certificate file path or content */);
var certWithKey = key.GetCertificateWithKey(certificate);
```
No caso de aplicações em **ASP.NET Core**, ao invés de pedir uma instância de @Lacuna.Pki.AzureConnector.IAzureCertificateProvider por injeção de dependência,
peça um @Lacuna.Pki.AzureConnector.IAzureKeyProvider e chame o método `GetKeyAsync()` quando necessário:
```cs
using Lacuna.Pki.AzureConnector;
public MyController : ApiController {
private readonly IAzureKeyProvider azureKeyProvider;
public MyController(IAzureKeyProvider azureKeyProvider) {
this.azureKeyProvider = azureKeyProvider;
}
...
public async Task Post() {
...
var key = await azureKeyProvider.GetKeyAsync(/* keyName */);
var certificate = PKCertificate.Decode(/* certificate file path or content */);
var certWithKey = key.GetCertificateWithKey(certificate);
var signer = new PadesSigner();
signer.SetPdfToSign(/* PDF file path, content or stream */);
signer.SetSigningCertificate(certWithKey);
signer.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(TrustArbitrators.PkiBrazil));
signer.ComputeSignature();
var signedPdf = signer.GetPadesSignature();
...
}
}
```
# [Entity Framework Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/ef-connector)
O pacote opcional
[Lacuna PKI Entity Framework Connector](https://www.nuget.org/packages/Lacuna.Pki.EntityFrameworkConnector/)
possibilita as seguintes integrações entre o SDK e o
[Microsoft Entity Framework](https://docs.microsoft.com/pt-br/ef/):
* Compressão e descompressão de assinaturas CAdES armazenando as LCRs e certificados em banco de dados
* Criação e validação de assinaturas CAdES com referência de revogação porém sem valores de revogação armazenando os valores em banco de dados
* Enviar mensagens de log geradas pelo SDK para o banco de dados
* Armazenamento de nonces criptográficos no banco de dados para autenticação com certificados digitais
> **Observação**
>
> O pacote assume que o Entity Framework está sendo usado sob o modelo Code First
## Utilizando armazenamento em banco de dados
A classe @Lacuna.Pki.EntityFrameworkConnector.EntityFrameworkStore, incluída no pacote opcional, implementa a interface
@Lacuna.Pki.Stores.ISimpleStore, utilizada pelo SDK quando um storage externo é necessário (para mais informações, veja
[Pacotes nuget opcionais](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages)).
Para utilizá-la, você deve fazer a classe `DbContext` do seu projeto implementar a interface
@Lacuna.Pki.EntityFrameworkConnector.IPkiStoreContext, o que basicamente significa adicionar um `DbSet` com objetos do
tipo @Lacuna.Pki.EntityFrameworkConnector.PkiStoreObject à sua classe:
```cs
public class MyDbContext : DbContext, IPkiStoreContext {
// ...
DbSet PkiStore { get; set; }
// ...
}
```
Se o seu projeto estiver usando *automatic migrations*, essa alteração ao `DbContext` irá fazer com que uma nova tabela
com nome "LacunaPkiStore" seja criada no banco de dados. Se o seu projeto estiver usando *code-based migrations*, as
alterações necessárias ao banco de dados aparecerão na próxima migração criada.
O próximo passo é instanciar a classe @Lacuna.Pki.EntityFrameworkConnector.EntityFrameworkStore passando uma instância
do seu `DbContext`, tipicamente dentro de um bloco `using`:
```cs
using (var dbContext = new MyDbContext()) {
// ...
var store = new EntityFrameworkStore(dbContext);
// ...
});
```
Tendo uma instância da classe `EntityFrameworkStore`, você pode utilizá-la em qualquer lugar que o SDK exija um
`ISimpleStore`, por exemplo para comprimir e descomprimir assinaturas CAdES:
```cs
byte[] precomputedSignature = ...; // qualquer assinatura CAdES, não necessariamente feita com o SDK
var compressedSignature = CadesSignatureCompression.Compress(precomputedSignature, store);
var decompressedSignature = CadesSignatureCompression.Decompress(compressedSignature, store);
```
> **Aviso**
>
> A classe `EntityFrameworkStore` **não chama** o método `SaveChanges()`, você mesmo precisa chamá-lo.
A motivação para o comportamento de não chamar o método `SaveChanges()` automaticamente é permitir que o programador
faça por exemplo uma compressão de assinatura CAdES como parte de uma transação. Portanto, assegure-se de que o método
`SaveChanges()` será chamado ao final do processo:
```cs
using (var dbContext = new MyDbContext()) {
// ...
byte[] precomputedSignature = ...; // qualquer assinatura CAdES, não necessariamente feita com o SDK
var store = new EntityFrameworkStore(dbContext);
var compressedSignature = CadesSignatureCompression.Compress(precomputedSignature, store);
// ...
dbContext.SaveChanges();
});
```
## Enviando logs para o banco de dados
A classe @Lacuna.Pki.EntityFrameworkConnector.EntityFrameworkLogger é utilizada para enviar logs gerados pelo SDK para
o banco de dados. Para utilizá-la, você deve fazer a classe `DbContext` do seu projeto imeplementar a interface
@Lacuna.Pki.EntityFrameworkConnector.IPkiLogContext, o que basicamente significa adicionar um `DbSet` com objetos do
tipo @Lacuna.Pki.EntityFrameworkConnector.PkiLogEntry à sua classe:
```cs
public class MyDbContext : DbContext, IPkiLogContext {
// ...
DbSet PkiLog { get; set; }
// ...
}
```
Se o seu projeto estiver usando *automatic migrations*, essa alteração ao `DbContext` irá fazer com que uma nova tabela
com nome "LacunaPkiLog" seja criada no banco de dados. Se o seu projeto estiver usando *code-based migrations*, as
alterações necessárias ao banco de dados aparecerão na próxima migração criada.
A partir de então, basta incluir no código de inicialização do seu site ou aplicação uma chamada ao método estático
`Lacuna.Pki.EntityFrameworkConnector.EntityFrameworkLogger.Configure(Lacuna.Pki.EntityFrameworkConnector.IPkiLogContext,LogLevels)`
da classe `EntityFrameworkLogger` passando uma instância do seu `DbContext`:
```cs
EntityFrameworkLogger.Configure(new MyDbContext());
```
> **Dica**
>
> A classe `EntityFrameworkLogger` chama automaticamente o método `SaveChanges()` do `DbContext` à medida que achar
> necessário. Portanto, você deve passar na chamada ao método `Configure()` uma instância exclusiva do seu `DbContext`.
## Utilizando armazenamento de nonces criptográficos em banco de dados
A classe @Lacuna.Pki.EntityFrameworkConnector.EntityFrameworkNonceStore, incluída no pacote opcional, implementa a
interface @Lacuna.Pki.Stores.INonceStore, utilizada pelo SDK para armazenar nonces criptográficos durante a autenticação
com certificados digitais.
Para utilizá-la, você deve fazer a classe `DbContext` do seu projeto implementar a interface
@Lacuna.Pki.EntityFrameworkConnector.IPkiNonceStoreContext, o que basicamente significa adicionar um `DbSet` com objetos
do tipo @Lacuna.Pki.EntityFrameworkConnector.PkiNonce à sua classe:
```cs
public class MyDbContext : DbContext, IPkiNonceStoreContext {
// ...
DbSet PkiNonces { get; set; }
// ...
}
```
Se o seu projeto estiver usando *automatic migrations*, essa alteração ao `DbContext` irá fazer com que uma nova tabela
com nome "LacunaPkiNonces" seja criada no banco de dados. Se o seu projeto estiver usando *code-based migrations*, as
alterações necessárias ao banco de dados aparecerão na próxima migração criada.
O próximo passo é instanciar a classe `EntityFrameworkNonceStore` passando uma instância do seu `DbContext`, tipicamente
dentro de um bloco `using`:
```cs
using (var dbContext = new MyDbContext()) {
// ...
var nonceStore = new EntityFrameworkNonceStore(dbContext);
// ...
});
```
Tendo uma instância da classe `EntityFrameworkNonceStore`, você pode utilizá-la para realizar autenticações com
certificados digitais.
```cs
public class AuthenticationController : ApiController {
public IHttpActionResult Get() {
byte[] nonce;
using (var dbContext = new DbContext()) {
var nonceStore = new EntityFrameworkNonceStore(dbContext);
var certAuth = new PKCertificateAuthentication(nonceStore);
nonce = certAuth.Start();
}
var response = new AuthenticationGetResponse() {
Nonce = nonce
};
return Ok(response);
}
public IHttpActionResult Post(AuthenticationPostRequest request) {
ValidationResults vr;
PKCertificate certificate;
using (var dbContext = new DbContext()) {
var nonceStore = new EntityFrameworkNonceStore(dbContext);
var certAuth = new PKCertificateAuthentication(nonceStore);
vr = certAuth.Complete(request.Nonce, request.Certificate, request.Signature, TrustArbitrators.Windows, out certificate);
}
// Check authentication result
if (!vr.IsValid) {
// This failure is perfectly normal, it happens everytime a user tries to sign in
// with an expired or revoked certificate, for instance.
return Ok(new AuthenticationPostResponse() {
Success = false,
Message = "Authentication failed",
ValidationResults = vr.ToString()
});
}
// ----------------------------------------------------------------------------------------
// At this point, you have assurance that the certificate is valid according to the
// TrustArbitrator you selected above and that the user is indeed the certificate's
// subject. Now, you'd typically query your database for a user that matches one of the
// certificate's fields, such as cert.EmailAddress or cert.PkiBrazil.CPF and set the user
// ID on the auth cookie. For demonstration purposes, we'll set the email address directly
// on the cookie as if it were the user ID.
// ----------------------------------------------------------------------------------------
FormsAuthentication.SetAuthCookie(certificate.EmailAddress, false);
// Inform the page of the success
return Ok(new AuthenticationPostResponse() {
Success = true
});
}
}
```
> **Dica**
>
> A classe `EntityFrameworkNonceStore` por padrão chama o método `SaveChanges()` por conta própria, não é necessário
> chamá-lo manualmente.
Para mais informações veja o tópico [Autenticação com certificado](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/auth).
## Dependência do pacote Entity Framework
Para maximizar a compatibilidade, o pacote depende de uma versão antiga do pacote EntityFramework. Entretanto,
recomendamos fortemente que seja utilizada a versão mais atual do pacote.
## Código-fonte
O código-fonte do pacote é aberto, ficando hospedado no
[BitBucket](https://bitbucket.org/Lacunas/pkientityframeworkconnector). Caso queira customizá-lo, você pode fazer um
fork do projeto e utilizar a sua versão customizada ao invés do pacote opcional.
# [NLog Connector](https://docs.lacunasoftware.com/articles/pki-sdk/optional-packages/nlog-connector)
O pacote opcional [Lacuna PKI Entity NLog Connector](https://www.nuget.org/packages/Lacuna.Pki.NLogConnector/)
possibilita enviar logs gerados pelo SDK para a biblioteca [NLog](http://nlog-project.org/).
Para isso, basta incluir a seguinte chamada no código de inicialização do seu site ou aplicação:
```cs
Lacuna.Pki.NLogConnector.NLogLogger.Configure();
```
A partir de então, todas as mensagens de log geradas pelo SDK serão repassadas ao NLog. A configuração do NLog,
entretanto, fica sob sua responsabilidade. O trecho abaixo mostra um exemplo de configuração do arquivo `web.config`
para enviar os logs para um arquivo no disco local:
```xml
```
> **Observação**
>
> É muito importante especificar logs assíncronos (`async="true"` na tag `targets`), caso contrário a performance do
> SDK pode ser consideravelmente impactada.
As mensagens são enviadas ao NLog com diferentes *logger names*, de acordo com o método interno do SDK que gerou a
mensagem. Todos os *logger names* começam com `Lacuna.Pki.` (por isso o atributo `name="Lacuna.Pki.*"` na tag `logger`
no exemplo acima).
## Dependência do pacote NLog
Para maximizar a compatibilidade, o pacote Lacuna PKI NLog Connector depende de uma versão antiga do pacote NLog.
Entretanto, recomendamos fortemente que seja utilizado versão mais atual do pacote.
## Código-fonte
O código-fonte do pacote Lacuna PKI NLog Connector é aberto, ficando hospedado no
[BitBucket](https://bitbucket.org/Lacunas/pkinlogconnector). Caso queira customizá-lo, você pode fazer um fork do
projeto e utilizar a sua versão customizada ao invés do pacote opcional.
# [Assinatura com Certificado Digital em Nuvem](https://docs.lacunasoftware.com/articles/pki-sdk/cloud-certificates)
O PKI SDK tem suporte a assinaturas com certificados digitais em nuvem, providos por prestadores de serviços em nuvem que seguem as especificações do ITI.
Os principais provedores são BirdId (Vault ID), ViDaaS (VALID), NeoId (Serpro), RemoteId (Certisign) e SafeId (Safeweb).
No entanto, o acesso ao certificado digital a ser utilizado na assinatura não é realizado pelo PKI SDK e sim pelo pacote `BrazilTrustServices`.
## Veja também
* [BrazilTrustServices](https://docs.lacunasoftware.com/articles/pki-sdk/cloud-certificates/brazil-trust-services)
**Demonstrações e exemplos**
- Demonstração: [Sign a PDF with your cloud certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-with-cloud-certificate)
- Sign a PDF with a cloud certificate (OAuth flow) using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesCloudOauthSdkController.cs)
- Sign a PDF with a cloud certificate (password flow) using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesCloudPwdSdkController.cs)
- Sign a PDF with a cloud certificate using CloudHub: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesCloudHubSdkController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureCloudhubRestController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-cloudhub-restpki.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-cloudhub-rest/index.php)
- Sign an XML document with a cloud certificate using CloudHub: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-signature-cloudhub-rest/index.php)
- Authenticate with a cloud certificate using CloudHub: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/AuthenticationCloudHubSdkController.cs) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/authentication-cloudhub.js)
# [Histórico de versões do PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/changelog)
### 2.22.7 (2026-09-28)
- \[PKI-160\] Atualiza o pacote Lacuna.T8 para a v1.2.29.
- \[PKI-160\] Corrige loop infinito (alto uso de CPU) ao ler PDFs com uma referência `/Parent` cíclica em campo de formulário.
### 2.22.6 (2026-09-11)
- \[PKI-159\] Atualiza o pacote Lacuna.T8 para a v1.2.28.
- \[PKI-159\] Corrige a perda da representação visual da assinatura em PDFs com metadados fora do padrão.
### 2.22.5 (2026-07-23)
- \[PKI-158\] Corrige falha do `PKCertificate.Decode` com string PEM em uma única linha.
### 2.22.4 (2026-07-06)
- Atualiza o pacote Lacuna.T8 para a v1.2.27.
- Corrige a detecção de assinaturas de PDF quando os números de objeto do dicionário de assinatura foram revisados.
- Corrige o carregamento de objeto AcroForm nulo em PDF.
### 2.22.3 (2026-04-06)
- \[PKI-155\] Melhora a decodificação do elemento identificador da política de assinatura XML, para aceitar aliases de Algorithm fora do padrão em vez da URI do algoritmo.
- \[PKI-156\] Atualiza as âncoras de confiança das TSLs PkiItaly e PkiPeru. Evita loop na validação de TSL.
### 2.22.2 (2026-03-05)
- Atualiza o pacote Lacuna.T8, removendo a tag de versão do T8 do dicionário de páginas.
### 2.22.1 (2026-01-27)
- \[PKI-153\] Corrige o método `NameGenerator.GenerateWithCommonNameOnly`, que não escapava caracteres de formatação.
### 2.22.0 (2026-01-26)
- \[PKI-152\] Melhora a codificação do campo CNPJ ICP-Brasil para dar suporte aos novos CNPJs alfanuméricos.
- Adiciona ao editor de assinaturas CAdES a funcionalidade de remover atributos não assinados.
### 2.21.2 (2025-11-28)
- \[PKI-150\] Requisição de carimbo de tempo sem o header `Expect: 100-continue` nos targets .NetFramework.
- Adiciona localização dos recursos em espanhol (es-ES).
### 2.21.1 (2025-11-07)
*Não disponível no nuget.org.*
- \[PKI-149\] Torna a propriedade SignatureAlgorithm do certificado de carregamento lazy.
### 2.21.0 (2025-10-27)
- \[PKI-146\] Adiciona ao `TimestampRequester` padrão a opção de um dicionário de headers personalizados.
- \[PKI-145\] Adiciona um provedor personalizado de DateTimeOffset para as operações de PKI: `IPkiTimeProvider`.
- \[PKI-138\] Corrige o texto da representação visual PAdES quando o PDF declara conformidade com PDF/A, mas não tem perfil ICC.
- \[PKI-147\] Corrige o texto e o traço das marcas de PDF em PDF/A sem OutputIntents.
- Melhora o `XmlSignatureEditor.ExtendArchiving`. Agora ele lança `InvalidArchiveOperationException` apenas para problemas conhecidos de validação ou de operação de arquivamento.
### 2.20.1 (2025-07-31)
- \[PKI-139\] Reverte as políticas de assinatura ICP-Brasil padrão das versões usadas na geração. Usar as versões mais recentes das políticas de assinatura passa a ser opcional (opt-in).
### 2.20.0 (2025-07-31)
- \[PKI-139\] Adiciona as novas políticas de assinatura ICP-Brasil, em conformidade, para validação e geração. Corrige a detecção de alterações não intencionais em revisões incrementais de assinaturas de PDF.
- \[PKI-139\] Adiciona o certificado raiz V12 da ICP-Brasil ao trust arbitrator PkiBrazil.
- \[PKI-144\] Melhora a listagem de PDFs com dicionários de assinatura corrompidos.
- \[PKI-139\] Melhora a assinatura de PDF.
- \[PKI-143\] Corrige o posicionamento visual em PDF para páginas com cropbox e rotação fora do centro.
- \[PKI-143\] Corrige a marca de PDF em nova página para páginas com cropbox herdado do pai.
### 2.19.0 (2025-07-25)
- \[PKI-141\] Adiciona configuração pública da política de itens de validação DSS/VRI do PAdES.
- \[PKI-140\] Melhora a validação do ByteRange com início deslocado (offset).
- \[PKI-142\] Corrige o posicionamento visual em PDF para páginas com cropbox e rotação.
### 2.18.1 (2025-05-09)
- \[PKI-133\] Substitui o uso do RSA legado da CAPI pelo CNG no método `GetSignatureCsp` baseado em chave pública.
- \[PKI-134\] Internacionalização (i18n) das mensagens de sucesso da validação.
- \[PKI-135\] Exige LTV nas políticas PAdES PkiBrazil AD-RB e AD-RT quando a entrada DSS já está presente no PDF.
### 2.18.0 (2025-03-10)
- \[PKI-130\] Adiciona métodos assíncronos ao `PKCertificateAuthentication`.
- \[PKI-132\] Adiciona as políticas públicas PAdES ICP-Brasil AD-RA e AD-RC para geração.
- \[PKI-129\] Melhora a listagem de assinaturas de PDF com `signatureFieldName` duplicado ou inconsistente. Atualiza o pacote Lacuna.T8.
### 2.17.2 (2024-09-09)
*Não disponível no nuget.org; o [PKI Express 1.32.1](https://docs.lacunasoftware.com/articles/pki-express/changelog#v1-32-1) a inclui.*
- \[PKI-127\] Melhora a inclusão e a validação do DocMDP.
- \[PKI-128\] Corrige exceção na decodificação do certificado do SigningCertificate com o esquema de assinatura RSA PSS, não suportado.
### 2.17.1 (2024-08-27)
- \[PKI-119\] Melhora a validação de assinaturas de PDF com o parâmetro ByteRange inválido.
- \[PKI-124\] Melhora a validação do cache local de LCRs (para pontos de distribuição de LCR que retornam LCRs inconsistentes).
- \[PKI-125\] Melhora a validação PAdES para políticas de arquivamento DSS obrigatórias.
- \[PKI-123\] Corrige o PAdES, que não usava os artefatos de revogação do DSS no primeiro signatário na chamada do método `ValidateAll`.
- \[PKI-126\] Corrige o posicionamento visual PAdES em PDFs com cropbox no tamanho de mídia da página.
### 2.16.1-beta01 (2024-06-13)
- \[PKI-99\] Corrige o uso do DSS do PAdES na validação do carimbo de tempo da assinatura na chamada do método `ValidateAll`.
### 2.16.0 (2024-04-16)
- \[PKI-112\] Adiciona as propriedades Nonce e bytes brutos do Nonce ao TstInfo
- \[PKI-117\] Adiciona suporte à decodificação de chaves privadas criptografadas
- \[PKI-113\] Melhora o erro genérico de "Append Mode" de PDF, que passa a ser InvalidPdfException
- \[PKI-116\] Melhora a validação de alterações em anotações de PDF
- \[PKI-118\] Melhora as mensagens em pt-BR dos resultados de validação
- \[PKI-114\] Melhora o suporte ao namespace legado do ArchiveTimeStamp do XAdES
- \[PKI-115\] Melhora os DateTimeOffsets nas mensagens de validação com o fuso horário personalizado PkiConfig.TimeZoneInfo
- \[PKI-109\] Corrige o SignatureFieldName personalizado de assinaturas PAdES, que era redefinido para o padrão em alguns cenários
### 2.15.0 (2023-07-28)
- \[PKI-102\] Adiciona a opção de X509Extension na geração de CSR
- \[PKI-102\] Adiciona a opção IssuingDistributionPoint à geração de X509Extension
- \[PKI-107\] Adiciona configuração global de TimeZoneInfo para strings de DateTimeOffset
- \[PKI-101\] Melhora o desempenho da decodificação de LCRs e de conjuntos de certificados
- \[PKI-101\] Melhora o desempenho de assinaturas conjuntas CAdES
- \[PKI-108\] Melhora a validação do valor de RangeBytes do PAdES
### 2.14.0-rc01 (2023-07-28)
- \[PKI-100\] Adiciona decodificação e geração da extensão Qualified Certificate Statements
- \[PKI-100\] Adiciona o elemento OrganizationIdentifier ao X509 Name
- \[PKI-100\] Melhora a geração de Name a partir de uma lista de OIDs e valores
### 2.13.2 (2023-06-29)
- \[PKI-104\] Corrige PdfMarks (o Adobe acusava erro na verificação da assinatura quando o PDF recebia marcas depois de assinado com LTV habilitado)
### 2.13.1 (2023-05-03)
- \[PKI-103\] Melhora a validação do message-imprint do DocTimeStamp de PDF
### 2.13.0 (2022-10-19)
- Adiciona ao PadesSigner a funcionalidade de preencher uma assinatura em branco pré-posicionada
- Melhora a verificação de assinatura para implementações de `IPrivateKey` de terceiros
- Melhora a listagem de assinaturas em PDFs inconsistentes
### 2.12.3 (2022-08-16)
- Adiciona a opção de Trust Arbitrator GovBR
- Adiciona o parâmetro de nome X.520 Description
- Amplia o conjunto de ACs estáticas da ICP-Brasil pré-carregadas
### 2.12.2 (2022-07-20)
- Melhora o `PdfContext` com informações de campos de formulário
- Melhora o `PdfMarker` com preenchimento de campos de formulário
### 2.12.1 (2022-07-10)
- Melhora os parâmetros de transferência de dados XML
### 2.12.0 (2022-06-27)
**Breaking change inesperado**
Sou afetado?
- Se você usa uma implementação personalizada de [`IPrivateKey`](https://docs.lacunasoftware.com/en-us/api/Lacuna.Pki.IPrivateKey), pode ser necessário atualizá-la
- Se você usa algum dos pacotes dependentes, da família `Lacuna.Pki.*`, pode ser necessário atualizar os pacotes para a versão minor mais recente
Mudanças:
- Adiciona a interface de criptografia (Encryption)
- Adiciona à assinatura de PDF os campos opcionais de nome e local do signatário
- Melhora o acesso do CSP de assinatura à PublicKey
- Melhora as opções de geração de PKCS#12
### 2.11.7 (2022-06-11)
- Corrige NullReferenceException no PdfMarker ao preservar a representação visual de assinaturas em alguns PDFs
### 2.11.6 (2022-06-06)
- Corrige exceção do T8 ao abrir PDFs: `Unable to cast object of type 'Lacuna.T8.text.pdf.PdfNull'`
### 2.11.5 (2022-05-07)
- Melhora a decodificação de URLs de ponto de distribuição de LCR mal formatadas
### 2.11.4 (2022-04-27)
- Adiciona a propriedade do padrão PDF/A ao PdfContext
### 2.11.3 (2022-04-25)
- Melhora a validação de XML e PDF com data de referência externa
### 2.11.2 (2022-04-22)
- Corrige ValidationException na assinatura de XML
### 2.11.1 (2022-04-13)
- Melhora a decodificação dos campos de certificados do Paraguai
- Adiciona o campo de certificado "Pasaporte" do Paraguai
### 2.11.0 (2022-04-02)
- Adiciona configuração de espaçamento entre linhas para PDF (funcionalidades PadesVisualText e PdfMarkText)
- Melhora o desempenho da assinatura e da validação PAdES
### 2.10.1 (2022-03-06)
- Corrige logs de aviso indesejados vindos da decodificação lazy de campos de PKIs de outros países
### 2.10.0 (2022-02-25)
- Melhora as opções da funcionalidade de geração de certificados
- Política de certificado com avisos ao usuário (user notices)
- Opção de marcar como crítica a extensão Extended Key Usage
- Opção de marcar como crítica a extensão Basic Constraints de entidade final
### 2.9.1 (2022-01-27)
- Melhora o suporte a PDFs com XMP codificado
- Melhora a remoção de assinaturas pelo PdfMarker
### 2.9.0 (2021-11-24)
- Adiciona a funcionalidade de escalonamento do conteúdo da página no PdfMarker
- Adiciona um editor de assinaturas XML com a funcionalidade de estender com carimbo de tempo de arquivamento
- Melhora o TimestampRequester com a opção de autenticação Basic pelo header Authorization
- Melhora a geração e a validação de OCSP
- Melhora o suporte a assinaturas em conformidade com PDF/A
### 2.8.12 (2021-08-19)
- Adiciona por padrão o elemento de informação X509Name do XAdES nas políticas da ICP-Brasil
- Melhora a segurança de threads (thread safety) na manipulação de PDFs
- Melhora o tratamento de erros na decodificação do campo DateOfBirth de certificados ICP-Brasil
- Corrige a ordem do X509Name do XAdES
- Corrige o redimensionamento automático da fonte das marcas de PDF
### 2.8.11 (2021-08-07)
- Melhora o tratamento de erros para criptografia de PDF não suportada
### 2.8.10 (2021-08-02)
- Adiciona ao signer info PAdES a verificação da presença de LTV
### 2.8.9 (2021-07-16)
- Melhora a validação padrão do PAdES com artefatos do DSS
- Melhora o suporte a PDFs com pequenas inconsistências
### 2.8.8 (2021-07-10)
- Melhora a validação de status da TSL (Trusted Service List)
- Corrige o limite de alinhamento vertical do texto do PdfMark
### 2.8.7 (2021-05-07)
- Adiciona a recuperação da posição da representação visual da assinatura de PDF a partir do signer info PAdES
- Adiciona um nível de validação de assinatura para algoritmos obsoletos
- Adiciona o PDfContext, com informações básicas do PDF e das assinaturas
- Adiciona exceção para PDF protegido por senha
- Define a funcionalidade Lapp como failover
- Melhora a verificação de permissões do PdfMarker
### 2.8.5 (2021-04-22)
- Corrige o comportamento do posicionamento automático da representação visual PAdES quando aplicada em várias páginas
### 2.8.4 (2021-03-26)
- Corrige o erro Bad Data ao carregar chave RSA de um PKCS#12. Afeta o .NET Framework 4.0, 4.5 e 4.6
### 2.8.3 (2021-03-24)
- Melhora o achatamento (flattening) do PdfMarker
### 2.8.2 (2021-03-19)
- Melhora a política de validação de assinaturas de PDF (sem atributos assinados)
- Melhora a política de validação de assinaturas CMS (sem atributos assinados)
### 2.8.0 (2021-01-11)
- Adiciona a funcionalidade de descrição na assinatura CAdES
- Adiciona a funcionalidade de descrição no DataObjectFormat do XAdES
- Adiciona o alinhamento vertical do texto na representação visual PAdES
- Adiciona o alinhamento vertical do texto no PdfMarker
- Melhora a validação do nome do emissor do certificado de assinatura no CAdES
### 2.7.9 (2020-09-17)
- Adiciona acesso à interface da chave privada no [PKCertificateWithKey](https://docs.lacunasoftware.com/api/Lacuna.Pki.PKCertificateWithKey#Lacuna_Pki_PKCertificateWithKey_PrivateKey)
- Adiciona uma classe mais simples para geração de certificados autoassinados: [SelfSignedPKCertificateGeneration](https://docs.lacunasoftware.com/api/Lacuna.Pki.Issuer.SelfSignedPKCertificateGenerator)
- Corrige o uso do algoritmo de canonicalização na assinatura XML de NFe
### 2.7.8 (2020-09-14)
- Atualiza as políticas de validação CAdES, PAdES e XAdES da ICP-Brasil
- Corrige a assinatura de XML com comentários
### 2.7.7 (2020-09-09)
- Define o c14n exclusivo como método de canonicalização padrão das assinaturas XML
- Adiciona a remoção forçada de namespaces padrão do XML nos elementos a assinar (que não é suprimida corretamente pela implementação de c14n do .NET; veja o [relato do problema](https://github.com/dotnet/runtime/issues/28844))
- Corrige a propagação de namespaces na validação de assinaturas XML
- Corrige o uso do ValidationContext na validação XAdES com carimbos de tempo de arquivamento
### 2.7.5 (2020-07-28)
- Adiciona a transformação XPath padrão em assinaturas XML enveloped
### 2.7.3 (2020-07-18)
- Adiciona a inclusão e a recuperação de metadados de PDF no PadesSigner, PadesSignature e PdfMarker
- Adiciona configuração global para priorizar a validação do status de revogação via OCSP em vez de LCR
- Adiciona a opção de representação visual na assinatura Document Timestamp de PDF
- Melhora os campos de certificados do Equador
- Melhora o cálculo da área necessária do PdfMarker com texto rotacionado
### 2.7.2 (2020-05-29)
- Adiciona ao signatário PAdES a opção de nome personalizado para o campo de assinatura
### 2.7.1 (2020-05-28)
- Adiciona a funcionalidade de extensões X509 personalizadas na geração de certificados
### 2.7.0 (2020-05-22)
- Adiciona os campos de certificado do Banco Central del Ecuador
- Adiciona os campos de certificado da PKI Argentina
- Adiciona campos de certificado ICP-Brasil que faltavam (ex.: RIC, CEI, Dados eleitorais, SIGEPE)
- Adiciona a opção de cor da fonte do texto à representação visual PAdES
- Melhora o desempenho do cache local de LCRs
### 2.6.1 (2020-05-07)
- Corrige caracteres não imprimíveis no stack trace
### 2.6.0 (2020-04-14)
- Adiciona fonte embutida na representação visual PAdES em PDF/A
- Adiciona a opção de cor de fundo à representação visual PAdES
- Adiciona políticas XAdES de longo prazo
- Adiciona o tipo TimestampException para falhas em atributos de carimbo de tempo obrigatórios
### 2.5.5 (2019-12-18)
- Adiciona a codificação dos atributos de certificado Access Identity e Authentication Info
- Atualiza o cache local de ACs da ICP-Brasil
- Corrige o carregamento do repositório de certificados de ACs em sistemas Unix
### 2.5.4 (2019-11-21)
- Corrige o cálculo da unidade pontos por centímetro do PdfMark.
- Corrige a obtenção do tamanho da representação visual da assinatura de PDF quando a definição do retângulo no PDF está malformada.
### 2.5.3 (2019-08-06)
- Melhora o template de nova página do PdfMarks.
- Adiciona ao PdfMarker a funcionalidade de preservar a representação visual das assinaturas.
### 2.5.2 (2019-07-16)
- Adiciona o AlphaCode (um gerador e manipulador de códigos alfabéticos pseudoaleatórios)
### 2.5.1 (2019-05-23)
- Adiciona os campos de certificado da PKI Paraguai
- Adiciona o controle das direções vertical e horizontal no posicionamento automático PAdES
### 2.4.0 (2019-04-08)
- Adiciona suporte ao LAPP
- Adiciona um codificador PEM de objetos
- Melhora o acesso aos artefatos de validação
### 2.3.0 (2019-02-22)
- Adiciona suporte à validação de certificados de curva elíptica
- Adiciona suporte à geração de certificados de curva elíptica
- Melhora o acesso aos artefatos de validação
- Corrige a validação de e-mail por regex
### 2.2.8 (2019-02-15)
- Melhora o configurador de validação do CadesSigner
### 2.2.7 (2018-12-26)
- Melhora as marcas de PDF com restrições do proprietário (owner)
### 2.2.6 (2018-12-17)
- Corrige a geração de Name com suporte a caracteres especiais
### 2.2.5 (2018-11-20)
- Melhora a validação PAdES com OCSP armazenado no DSS
- Melhora a ordem de geração dos campos de Name
- Melhora a geração de certificados autoassinados
### 2.2.4 (2018-11-09)
- Corrige a validação rápida de certificado do CadesSigner com um configurador mais permissivo
### 2.2.3 (2018-11-01)
- Melhora o uso de memória em assinaturas PAdES
- Adiciona exceção para arquivo de assinatura CAdES inválido
### 2.2.2 (2018-10-17)
- Adiciona o motivo (reason) funcional ao signatário PAdES
- Adiciona algoritmos de digest compatíveis com FIPS no target .NETFramework 4.0 Client
- Adiciona o atributo Role para certificados de atributo
- Corrige bug na rotação de marcas de PDF
### 2.1.3 (2018-08-13)
- Adiciona parâmetro opcional da TSA: OID da política da TSA
- Melhora as políticas de codificação de Name
- Corrige o finalizador de assinaturas de arquivamento
### 2.1.0 (2018-07-16)
- Adiciona suporte a pacote multi-target: .NETStandard 2.0, .NETFramework 4.5 e .NETFramework 4.0 Client
### 1.20.16 (2018-05-15)
- Corrige bug no preenchimento da cadeia de certificação no PAdES
### 1.20.15 (2018-05-08)
- Atualiza o repositório local de certificados de ACs da ICP-Brasil
### 1.20.14 (2018-05-08)
- Melhora a decodificação de extensões de certificado malformadas
- Melhora a validação de OCSP nocheck
### 1.20.12 (2018-01-30)
- Adiciona configuração de proxy para as requisições web do PKI SDK
### 1.20.10 (2018-01-17)
- Adiciona suporte à configuração personalizada de validação de certificados para os signatários
- Adiciona uma configuração predefinida (preset) de validação offline do certificado do signatário
### 1.20.9 (2017-12-27)
- Adiciona as propriedades de padrão PDF/A e de versão do PDF ao PadesSignature
- Adiciona a opção de gerar PKCertificate sem a URI da política de certificado
- Melhora o utilitário de codificação ASN1 para tipos primitivos
### 1.20.8 (2017-12-13)
- Adiciona a opção de número de série à geração de certificados de chave pública
### 1.20.7 (2017-12-06)
- Adiciona o Id do elemento de assinatura ao XmlSignature
- Corrige o caminho do cache de LCRs, que diferenciava maiúsculas de minúsculas
- Corrige avisos de algoritmo de assinatura não suportado no repositório de certificados do Windows
### 1.20.6 (2017-11-21)
- Adiciona a opção de atributo de tipo de compromisso (commitment type) para assinaturas XAdES e PAdES
- Corrige o atributo de tipo de compromisso não reconhecido no CAdES
- Corrige o PdfMarks, que não respeitava o comportamento anterior de lançar exceções
### 1.20.5 (2017-11-20)
- Corrige o signing-time do PAdES
### 1.20.4 (2017-11-17)
- Adiciona a funcionalidade de contexto de validação para validar assinaturas CAdES, PAdES e XAdES
- Adiciona um método de verificação de parâmetros às classes de signatário
- Adiciona a opção de atributo de tipo de compromisso para assinaturas CAdES
- Corrige a possibilidade de loop na montagem da cadeia
### 1.20.1 (2017-10-26)
- Pacote multi-target com suporte ao .NET Framework 4.5 e 4.0 Client Profile
- Usa o novo formato de csproj
### 1.19.5 (2017-10-02)
- Adiciona o objeto Certificate Signing Request (PKCS#10)
- Melhora a codificação de respostas RFC 3161
- Melhora a decodificação dos campos da PKI Peru
- Atualiza a biblioteca T8
### 1.19.4 (2017-09-14)
- Melhora a decodificação dos nomes do emissor e do titular
### 1.19.3 (2017-09-07)
- Melhora a assinatura PAdES completa
- Adiciona os valores de revogação do carimbo de tempo e do signatário depois de concluída a assinatura PAdES com LTV
- Corrige a extensão de pontos de distribuição de LCR com várias URIs
- Corrige bug na decodificação de status OCSP malformado
### 1.19.1 (2017-07-27)
- Remove o algoritmo de hash SHA1 dos algoritmos permitidos nas políticas de assinatura padrão. Afeta as políticas XadesBasic, XmlDSigBasic, carimbos de tempo Xades AdrTempo, Pades Basic, Italy CadesBes, CadesBes, CadesT, CadesC, CadesXLong, CadesXType1, CadesXLongType1 e CadesA
### 1.19.0 (2017-07-25)
- Adiciona a funcionalidade de proteção de PDF com senha
- Adiciona intervalo de tempo (timespan) para a validação de OCSP
- Corrige o preenchimento do emissor no OCSP
- Corrige bug em níveis de validação personalizados definidos para assinatura
### 1.18.0 (2017-07-04)
- Melhora as marcas de PDF
### 1.17.1 (2017-06-21)
- Adiciona a geração de certificados X509 de chave pública
- Adiciona a geração de PKCS#12
### 1.16.9 (2017-06-10)
- A validação de assinaturas de PDF com data de referência passa a considerar também o conjunto de dados de revogação do SignedData do CMS, se presente
### 1.16.8 (2017-05-25)
- Adiciona a propriedade PadesVisualText.SigningTimeOffset
### 1.16.7 (2017-05-10)
- Corrige a validação de assinaturas XML, que lançava UnacceptableSignaturePolicyException
- Corrige a redundância de dados de imagem nas marcas de PDF
- Adiciona a URI ao log de trace de download de dados
### 1.16.6 (2017-04-26)
- Corrige a atualização da licença em produto restrito
- Nova validação de produto restrito
### 1.16.5 (2017-03-16)
- Atualiza as ACs intermediárias da ICP-Brasil
### 1.16.4 (2017-03-01)
- Mudanças na inspeção e na compressão de assinaturas CAdES para lidar com carimbos de tempo codificados incorretamente
### 1.16.3 (2017-02-15)
- Adiciona os campos de certificado da PKI Peru
### 1.16.2 (2017-02-07)
- Adiciona mapeadores de políticas XML
- Política NFe sem IDs nos elementos de assinatura
### 1.16.1 (2017-01-23)
- Corrige o último merge de hotfix
### 1.16.0 (2017-01-16)
- Adiciona empacotamento condicional via grunt para os frameworks Net4.0 e Net4.5
- Adiciona exceção de PDF inválido no PdfMarker
- Adiciona mais parâmetros de Name
- Adiciona suporte à validação PAdES com data de referência
- Adiciona suporte à geração de CSR
- Adiciona a opção de licenciamento por funcionalidades
- Melhora os campos ICP-Brasil. Os campos também retornam null quando têm o valor de preenchimento padrão
- Melhora a obtenção de instâncias de algoritmos de digest a partir de vários padrões de nome
- Melhora a assinatura PAdES para carregar e validar assinaturas de PDF adbe.pkcs7.sh1
- Melhora os downloads internos
- Corrige o estado UnderSupervision da TSL, agora considerado válido
- Corrige atraso no carregamento do repositório de certificados causado por tentativa de carregar a cadeia de certificação
- Corrige bug com o provider Microsoft Base Cryptographic Provider v1.0
- Corrige a codificação do carimbo de tempo de arquivamento CAdES
- Corrige linha vazia na representação visual PAdES
- Corrige o posicionamento incorreto das marcas de PDF e da representação visual PAdES em alguns PDFs rotacionados
- Corrige exceção na decodificação de alguns campos ICP-Brasil
### 1.13.5 (2016-12-08)
- Adiciona novas opções de política de assinatura XML
### 1.13.4 (2016-08-15)
- Corrige bug que ocorria em algumas assinaturas de PDF
### 1.13.3 (2016-07-20)
- Adiciona T8 versionado
### 1.13.2 (2016-07-14)
- Corrige a ordem do atributo de referências completas de revogação no CAdES
- Atualiza a URI e a raiz da TSL italiana
### 1.13.1 (2016-07-07)
- Corrige bug que afetava a assinatura de alguns PDFs grandes
### 1.13.0 (2016-07-05)
- Adiciona as políticas de assinatura PAdES da ICP-Brasil (v1.0)
- Adiciona as novas políticas de assinatura CAdES da ICP-Brasil (v2.2 e v2.3)
- Adiciona as novas políticas de assinatura XAdES da ICP-Brasil (v2.3)
- Adiciona a emissão assíncrona de LCR e OCSP
- Corrige problema que afetava a validação do emissor do OCSP
- Corrige problema na definição do algoritmo de digest do signer-info CAdES
- Corrige bug que afetava o uso de certificados PKCS#12 em cenários raros
### 1.12.2 (2016-06-10)
- Adiciona exceção de parâmetro da representação visual
### 1.12.1 (2016-06-03)
- Adiciona suporte à decodificação dos campos "OAB" e "RG" de certificados ICP-Brasil
- Adiciona à geração de certificados de atributo o suporte à extensão AuthorityKeyIdentifier
- Introduz a nova classe CadesAcceptablePoliciesCatalog para simplificar a validação de assinaturas CAdES que aceitam várias políticas de assinatura
- Introduz a nova classe Rfc3161Encoding, que permite às aplicações implementar o protocolo de carimbo de tempo (TSP)
- Introduz a classe de exceção especializada TimestampRequestException para identificar erros ao requisitar carimbos de tempo
- Adiciona suporte a certificados com o raro OID alternativo do algoritmo de assinatura SHA-1 com RSA (1.3.14.3.2.29)
- Introduz a nova classe FileSystemNonceStore, uma implementação sem dependências da interface INonceStore
### 1.12.0 (2016-05-02)
- Introduz a nova classe PdfMarker para aplicar marcas em PDF
- Introduz a nova interface ITimestampRequester, que permite personalizar a obtenção de carimbos de tempo
- Adiciona suporte à personalização da cultura, do formato e do fuso horário da data da assinatura na representação visual PAdES
- Adiciona suporte a assinaturas CAdES (sem conteúdo encapsulado) passando apenas o digest da mensagem
- Adiciona suporte à inclusão de conteúdo encapsulado na assinatura CAdES
- Adiciona a raiz confiável "v5" da ICP-Brasil
- Corrige problema que afetava a validação de assinaturas XML com declarações de namespace no elemento Signature
- Corrige problema que afetava o posicionamento das representações visuais PAdES em cenários específicos com vários signatários
- Corrige problema que causava diálogos de PIN desnecessários ao listar certificados
### 1.11.2 (2016-03-27)
- Corrige bug na decodificação de LCRs quando o ReasonCode está presente
- Melhora as mensagens da validação do status de revogação de certificados
### 1.11.1 (2016-03-24)
- Corrige bug na validação do status de revogação de certificados que causava stack overflow em cenários raros de validação OCSP
### 1.11.0 (2016-02-22)
- O SDK não exige mais a biblioteca iTextSharp, de licença AGPL, para fazer assinaturas PAdES
- O pacote Lacuna.Pki.PadesConnector foi descontinuado (deprecated), já que as assinaturas PAdES agora são feitas internamente pelo SDK
### 1.10.1 (2016-02-15)
- Corrige bug na assinatura PAdES que causava incompatibilidade com certas TSAs que emitiam carimbos de tempo maiores que
o esperado
- Corrige bug na geração de certificados de atributo que impedia a inclusão da extensão AuthorityInformationAccess
quando o CAIssuersUri era informado
- Corrige bug na validação de certificados de atributo referente à validação da restrição básica "IsCA" no certificado
do emissor
- Corrige bug na validação de certificados de atributo quando a extensão NoRevocationAvailable está presente
### 1.10.0 (2016-02-13)
- Adiciona suporte a chaves privadas extensíveis pelo usuário
- Adiciona à classe Asn1Util suporte à codificação do tipo ASN.1 "sequence of"
- Adiciona suporte à decodificação da extensão de certificado "DNS Names"
- Adiciona suporte para limpar a licença do SDK carregada anteriormente
### 1.9.1 (2016-02-11)
- Corrige bug na codificação da estrutura ASN.1 AlgorithmIdentifier que fazia o campo "parameters" ser omitido em vez
de preenchido com o valor NULL
### 1.9.0 (2016-01-21)
- Adiciona suporte a assinaturas XML (XmlDSig/XAdES)
- Adiciona suporte à propagação de alterações nos trust arbitrators de uma política de assinatura para as políticas de validação de carimbo de tempo dela
- Melhora a validação de certificados para verificar a extensão PathLenConstraint
- Melhora a validação de assinaturas CAdES nos casos em que o certificado do signatário não pôde ser encontrado
- Corrige bug na validação de licenças sem o campo "uso restrito"
- Corrige bug na validação de assinaturas CAdES que fazia ser informado o ValidationItem errado quando nenhum carimbo de tempo
de assinatura válido era encontrado (informava InvalidReferencesTimestamp; o correto é InvalidSignatureTimestamp)
### 1.8.0 (2015-11-23)
- Adiciona à representação visual PAdES suporte à definição de um container dentro do retângulo da assinatura no qual
posicionar o texto
### 1.7.2 (2015-11-12)
- Modifica o comportamento da propriedade IcpBrasilCertificateFields.CompanyName para retornar o nome da empresa quando o certificado é
um certificado ICP-Brasil de empresa (PJ) (antes, a propriedade só funcionava para certificados ICP-Brasil de aplicação)
- Corrige bug na política de assinatura CAdES ICP-Brasil AD-RC v2.1 (o OID da política estava incorreto)
- Corrige bug na validação de assinaturas CAdES que usam políticas implícitas
### 1.7.1 (2015-10-30)
- Adiciona suporte ao campo CPF ICP-Brasil no campo "OU" do nome do titular com um espaço depois dos dois-pontos ("OU=CPF: xxxxxxxxxxx")
### 1.7.0 (2015-10-27)
- Adiciona suporte a licenças com uso restrito a determinados produtos da Lacuna
- Modifica o comportamento da classe IcpBrasilCertificateFields para tentar decodificar os campos independentemente de o certificado
parecer ou não um certificado ICP-Brasil
- *Observação: a forma correta de verificar se um certificado é um certificado ICP-Brasil válido é validá-lo com o
trust arbitrator TrustArbitrators.PkiBrazil, e não pelos seus campos*
### 1.6.0 (2015-10-14)
- Adiciona à representação visual PAdES suporte ao alinhamento horizontal do texto à direita
- Introduz a classe PkiInfo, com informações sobre a licença do SDK carregada
- Corrige bug no log que fazia o argumento "source" ter um valor incorreto
### 1.5.8 (2015-09-30)
- Adiciona a verificação do valor da assinatura no PadesSigner
- Corrige bug que fazia as classes WindowsCertificateStore e Pkcs12CertificateStore retornarem uma lista vazia de
certificados em vez de lançar uma exceção quando a licença do SDK não era encontrada
- Corrige bug na compatibilidade retroativa com a antiga representação visual PAdES
### 1.5.7 (2015-09-28)
- Refatora a representação visual PAdES (introduz a nova classe PadesVisualRepresentation2)
- Corrige bug na política CAdES da Itália que fazia certificados e LCRs adicionais serem incluídos indevidamente no SignedData
### 1.5.6 (2015-08-31)
- Adiciona suporte ao fornecimento de um repositório de certificados externo à classe PadesSigner
- Corrige bug na verificação do status de revogação de certificados quando era necessário um repositório de certificados externo
- Corrige bug nas propriedades DateOfBirth e CertificateType da classe IcpBrasilCertificateFields que fazia ser lançada uma exceção
se o certificado tivesse sido mal emitido/codificado (agora o comportamento é retornar null)
### 1.5.5 (2015-08-14)
- Modifica o licenciamento para aceitar apenas arquivos de licença com a extensão .config, para evitar o vazamento dos arquivos de licença
- *Observação: licenças emitidas antes da data desta versão continuam sendo aceitas com a extensão .xml*
### 1.5.4 (2015-07-30)
- Melhora a verificação do status de revogação de certificados
- Melhora a validação de assinaturas PAdES para considerar carimbos de tempo de assinatura internos
### 1.5.3 (2015-07-30)
(versão não lançada)
### 1.5.2 (2015-07-23)
- Adiciona suporte à autenticação por senha com servidores de carimbo de tempo
- Melhora as mensagens de validação para erros na verificação do status de revogação
- Melhora a documentação do IntelliSense
- A propriedade PkiConfig.MaxDelay foi descontinuada (deprecated) em favor da propriedade PkiConfig.PreemptiveDownloadMaxDelay
### 1.5.1 (2015-07-16)
- Primeira versão disponível publicamente
# [Gerando códigos alfanuméricos](https://docs.lacunasoftware.com/articles/pki-sdk/alpha-codes)
Ao gerar uma versão para impressão de um arquivo assinado, é necessário gerar um *código de verificação* a ser
incluido no documento para que um terceiro que porventura receba o documento impresso possa visitar o seu site e fornecer o
código, obtendo assim a versão assinada digitalmente (que é a que efetivamente tem validade jurídica):

No passado, nós províamos o código-fonte para geração desse código de verificação como parte dos exemplos, a ser copiado
para o código da sua aplicação. Por exemplo:
```cs
public static class Util {
// ...
public static string GenerateVerificationCode() {
// String with exactly 32 letters and numbers to be used on the codes.
const string Alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
// Allocate a byte array large enough to receive the necessary entropy
var bytes = new byte[(int)Math.Ceiling(VerificationCodeSize * 5 / 8.0)];
// ...
return sb.ToString();
}
// ...
}
```
Entretanto, como o código de verificação desempenha um papel crucial na proteção do acesso aos seus documentos, nós
agora oferecemos a classe `AlphaCode` para fazer a geração dos códigos.
## Atualizando sua aplicação para usar a classe *AlphaCode*
> **Aviso**
>
> A lógica de geração de códigos de verificação passou por uma auditoria minuciosa e recebeu importantes melhorias.
> Por isso, recomendamos que você atualize a sua aplicação para utilizar a classe *AlphaCode* ao invés de utilizar
> o código-fonte antigo.
Você provavelmente copiou anteriormente os métodos (agora obsoletos) `GenerateVerificationCode`, `FormatVerificationCode` e `ParseVerificationCode`
dos exemplos para o seu código. Para atualizar a sua aplicação:
1. Atualize o pacote de nuget [Lacuna.Pki](https://www.nuget.org/packages/Lacuna.Pki) para a versão **2.5.2** ou superior
1. Substitua a implementação desses métodos no seu código por chamadas aos métodos da classe *AlphaCode*:
```cs
public static string GenerateVerificationCode() => AlphaCode.Generate();
public static string FormatVerificationCode(string code) => AlphaCode.Format(code);
public static string ParseVerificationCode(string formattedCode) => AlphaCode.Parse(formattedCode);
```
## Princípios de projeto
A classe *AlphaCode* gera códigos alfanuméricos feitos para serem lidos por pessoas seguindo os princípios abaixo:
1. Os códigos devem de fácil leitura
1. Os códigos devem poder ser facilmente digitados com baixo risco de confundir caracteres similares como `O` e `0`
1. Os códigos devem ter relativa alta entropia com relação ao tamanho do código (alto número de possíveis códigos relativo ao tamanho
do código, permitindo ao desenvolvedor escolher um código de tamanho relativamente pequeno)
Para melhorar a legibilidade, são utilizadas apenas letras maiúsculas e não são utilizados os caracteres `O`, `0`, `1` e `I`. A entropia
por caractere é relativamente alta, se comparada com um código hexadecimal: como existem 32 possíveis caracteres, cada caractere contribui
com 5 bits à entropia total do código gerado (25% a mais do que num código hexadecimal), o que resulta em códigos menores para uma dada entropia mínima.
Por exemplo, para gerar um código com 80 bits de entropia (2^80 possíveis códigos):
* Em hexadecimal, seria necessário um código de 20 caracteres, ex: `90A0-F20F-5883-8D55-AD31`
* Com a classe *AlphaCode*, o código teria apenas 16 caracteres, ex: `FFWC-RHC5-9NLF-VM42`
## Veja também
* [Versão para impressão](https://docs.lacunasoftware.com/articles/pki-sdk/printer-friendly-version): marque um PDF assinado com o código usando o `PdfMarker`
* [Gerando códigos alfanuméricos em .NET (Rest PKI)](https://docs.lacunasoftware.com/articles/rest-pki/dotnet/alpha-codes)
* [Lacuna PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk)
**Demonstrações e exemplos**
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
- Make a printer-friendly version of a signed PDF using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyPadesSdkController.cs)
# [Configurações](https://docs.lacunasoftware.com/articles/pki-sdk/config)
Algumas configurações do SDK como tempo de timeout de downloads internos, diretório para cache e arquivos temporários
da biblioteca, uso de download preemptivo de CRLs, entre outros, podem ser alteradas através das propriedades e campos
da classe `Lacuna.Pki.PkiConfig`.
Verifique o guia da API para mais informações.
## Veja também
* @Lacuna.Pki.PkiConfig
# [Suporte a .NET Standard / .NET Core](https://docs.lacunasoftware.com/articles/pki-sdk/net-standard)
A partir da versão 2.1.0, o [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk) suporta o **[.NET Standard](https://learn.microsoft.com/pt-br/dotnet/standard/net-standard) 2.0**,
além do .NET Framework. Isso significa que o PKI SDK pode ser utilizado em aplicações escritas em [.NET Core](https://learn.microsoft.com/pt-br/dotnet/core/introduction).
O pacote `Lacuna.Pki` tem como alvo estes frameworks:
| Versões | .NET Standard | .NET Framework |
| --- | --- | --- |
| 2.13.0 e posteriores | 2.0 | 4.5, 4.6 e 4.7 |
| 2.3.0 a 2.12.3 | 2.0 | 4.0 Client Profile, 4.5, 4.6 e 4.7 |
| 2.1.0 a 2.2.8 | 2.0 | 4.0 Client Profile e 4.5 |
> **Dica**
>
> Aplicações em .NET Core possuem o importante diferencial de serem **compatíveis com servidores Linux**, aumentando a cobertura
> de plataformas suportadas pelo seu sistema.
Para demonstrar as possibilidades trazidas por essa novidade, a nossa equipe de desenvolvimento realizou com sucesso em
laboratório uma assinatura digital em um Raspberry Pi 3 em uma aplicação console .NET Core utilizando o PKI SDK.
Além disso, é possível também utilizar o PKI SDK em apps mobile escritos em [Xamarin](https://visualstudio.microsoft.com/xamarin/).
# [Versão para impressão](https://docs.lacunasoftware.com/articles/pki-sdk/printer-friendly-version)
A cópia impressa de um documento assinado não leva as assinaturas digitais. A versão para impressão compensa isso: cada
página mostra quem assinou o documento e um código de verificação, para que quem receber o papel possa visitar o seu
site, informar o código e obter de volta o arquivo assinado digitalmente.
No PKI SDK, a classe `AlphaCode` gera o código de verificação e o `PdfMarker` (namespace `Lacuna.Pki.Pdf`) grava marcas,
compostas de textos e imagens, nas páginas de um PDF.
## Como funciona
1. Na primeira vez em que um documento for solicitado, gere o código de verificação dele com `AlphaCode.Generate()` e
guarde-o junto com o documento. O código é gerado sem formatação: mostre-o aos usuários com `AlphaCode.Format`, por
exemplo como `XXXX-XXXX-XXXX-XXXX`. Veja [Gerando códigos alfanuméricos](https://docs.lacunasoftware.com/articles/pki-sdk/alpha-codes).
1. Abra o PDF assinado com `PadesSignature.Open` e leia os signatários em `Signers`, por exemplo o nome no certificado de
cada um e a data e hora da assinatura.
1. Crie um `PdfMarker` e adicione um `PdfMark` por marca com `AddMark`.
1. Defina `ThrowIfSignedPdf` como `false`. O padrão é `true`, e com ele o `PdfMarker` lança `SignedPdfToMarkException`
(namespace `Lacuna.Pki.Exceptions`) se o PDF tiver qualquer assinatura.
1. Chame `WriteMarks(pdfContent)`, que retorna o PDF com as marcas. Todas as assinaturas são removidas do arquivo antes de
as marcas serem aplicadas, então a versão para impressão não é um arquivo assinado.
1. Na sua página de verificação, converta o código digitado pelo usuário para a forma guardada com `AlphaCode.Parse`,
localize o documento e valide as assinaturas como de costume, com `PadesSignature.Open` e `ValidateSignature`. O
exemplo espera dois segundos antes de responder a um código desconhecido, para dificultar ataques de força bruta.
## Exemplo
Adaptado do exemplo: um resumo na margem inferior de todas as páginas e uma nova página no fim do documento com o código
de verificação.
```cs
var formattedCode = AlphaCode.Format(verificationCode);
// Names of the signers, for the summary
var signature = PadesSignature.Open(pdfContent);
var signerNames = string.Join(", ", signature.Signers.Select(s => s.Signer.SigningCertificate.SubjectName.CommonName));
var summary = $"This document was digitally signed by {signerNames}.\nTo verify the signatures go to {verificationSite} and inform the code {formattedCode}";
var pdfMarker = new PdfMarker();
// Summary on the bottom margin of every page
pdfMarker.AddMark(new PdfMark() {
PageOption = PdfMarkPageOptions.AllPages,
Container = new PadesVisualRectangle() { Height = 2, Bottom = 0, Left = 1.5, Right = 3.5 },
Elements = new List() {
new PdfMarkText() {
Texts = new List() {
new PdfTextSection() { Style = PdfTextStyle.Normal, Text = summary }
}
}
}
});
// New page at the end of the document, with 1-inch margins
var summaryPage = new PdfMark() {
PageOption = PdfMarkPageOptions.NewPage,
Container = new PadesVisualRectangle() { Top = 2.54, Bottom = 2.54, Left = 2.54, Right = 2.54 }
};
summaryPage.Elements.Add(new PdfMarkText() {
RelativeContainer = new PadesVisualRectangle() { Top = 0, Height = 2, Left = 0, Right = 0 },
Align = PadesHorizontalAlign.Center,
Texts = new List() {
new PdfTextSection() { Style = PdfTextStyle.Normal, FontSize = 14, Text = $"Verification code: {formattedCode}" }
}
});
pdfMarker.AddMark(summaryPage);
pdfMarker.ThrowIfSignedPdf = false; // the PDF is signed: don't throw SignedPdfToMarkException
var pfvContent = pdfMarker.WriteMarks(pdfContent);
```
A página de resumo do exemplo traz também o logotipo da ICP-Brasil, um QR code com o link de verificação (uma imagem PNG
gerada com a biblioteca QRCoder) e uma linha por signatário, com um ícone verde ou vermelho conforme o resultado de
`ValidateSignature`. O exemplo também põe o resumo na margem direita de todas as páginas, girado com
`PdfMarkRotation.D90` para que o texto fique na vertical, de baixo para cima.
## Marcas
Um `PdfMark` é uma marca aplicada nas páginas do PDF:
| Propriedade | O que define |
| --- | --- |
| `PageOption` | As páginas que recebem a marca, um `PdfMarkPageOptions`: `AllPages`; `NewPage`, uma nova página acrescentada no fim do documento; ou `SinglePage` e `SinglePageFromEnd`, junto com a propriedade `PageOptionNumber`. |
| `Container` | A posição da marca na página, um `PadesVisualRectangle`: a classe que posiciona a representação visual das assinaturas, explicada em [Definindo um container](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades/visual-rep/containers). |
| `MeasurementUnits` | `PadesMeasurementUnits.Centimeters` ou `PadesMeasurementUnits.PdfPoints`. O exemplo não a define, e as medidas dele estão em centímetros (2,54 para margens de 1 polegada). |
| `Elements` | Os textos e as imagens da marca: uma lista de `PdfMarkText` e `PdfMarkImage`. |
| `BorderWidth`, `BorderColor`, `BackgroundColor` | A borda e o fundo da marca. |
Todo elemento tem um `RelativeContainer`, a posição dele dentro do `Container` da marca; uma `Rotation`, `D0`, `D90`,
`D180` ou `D270` (em graus); e uma `Opacity`, 100 (opaco) por padrão. Além disso:
- O `PdfMarkText` tem `Texts`, uma lista de `PdfTextSection`, cada um com seu `Text`, `Style` (`Normal`, `Bold`, `Italic`
ou `Underline`, que podem ser combinados), `FontSize` e `Color` (preto por padrão); e `Align`, `VerticalAlign` e
`LineSpacing`, um multiplicador do tamanho da fonte.
- O `PdfMarkImage` tem `ImageContent`, os bytes da imagem; o exemplo usa imagens PNG.
## Outras opções
- `PreserveSignaturesVisualRepresentation`: mantém no PDF as imagens da representação visual das assinaturas, se houver.
- `NewPagesTemplate`: um `PdfPageTemplate` com o tamanho e a orientação das páginas criadas pelas marcas `NewPage`. Se não
for definido, é usado o tamanho de papel mais comum entre as páginas do PDF, em retrato.
- `CreateContext` e `ComputeRequiredArea`: calculam a área de que um texto precisa antes de você posicioná-lo; depois,
chame `WriteMarks(context)`.
- `WriteMarks(Stream, Stream)` lê o PDF de um stream e grava o resultado em outro.
## Assinaturas CAdES
Um arquivo CMS/CAdES não é um PDF, então o exemplo de CAdES monta a versão para impressão como um documento novo: ele lê
os signatários com `CadesSignature.Open` e grava a marca de resumo num PDF em branco que acompanha o exemplo.
## Veja também
* [Gerando códigos alfanuméricos](https://docs.lacunasoftware.com/articles/pki-sdk/alpha-codes)
* [Assinaturas padrão PAdES](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/pades)
* [`PdfMarker` na referência da API](https://docs.lacunasoftware.com/api/Lacuna.Pki.Pdf/PdfMarker)
* [Lacuna PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk)
**Demonstrações e exemplos**
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
- Make a printer-friendly version of a signed PDF using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyPadesSdkController.cs)
- Make a printer-friendly version of a CMS/CAdES signature using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyCadesSdkController.cs)
# [Referência rápida do PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference)
O essencial do PKI Express em uma página, com links para as páginas completas.
## Para que serve
O PKI Express cria assinaturas digitais CMS/CAdES, PDF/PAdES e XmlDSig/XAdES a partir de praticamente qualquer linguagem de programação, nos seus próprios servidores Windows ou Linux.
Você o instala no servidor e o aciona por uma biblioteca cliente para Java, Node.js, PHP, Python ou Ruby; para assinar com o certificado do usuário numa página web, ele trabalha junto com o Web PKI.
| Alternativa | Quando é a melhor escolha |
|---|---|
| [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/quick-reference) | Sua aplicação é .NET: o PKI SDK é uma biblioteca para aplicações .NET, e o guia de certificação digital o recomenda para assinaturas no servidor em .NET |
| [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/quick-reference) | Você prefere chamar uma API REST a instalar software nos seus servidores: use-o como serviço em core.pki.rest ou numa instância própria; ele também hospeda a página em que o usuário assina |
| [Web PKI](https://docs.lacunasoftware.com/articles/web-pki/quick-reference) | Você precisa da parte do navegador: listar os certificados do usuário e assinar com eles numa página web. Ele complementa o PKI Express, em vez de substituí-lo |
| [Signer](https://docs.lacunasoftware.com/articles/signer/quick-reference) | Você quer enviar documentos para pessoas e colher as assinaturas delas com um fluxo pronto, como SaaS ou on-premises, em vez de construir o fluxo |
## Instalação
Duas partes: o próprio PKI Express, em cada servidor que executa a sua aplicação, e o pacote cliente da sua linguagem.
| Linguagem | Pacote | Major atual | Registro |
|---|---|---|---|
| PKI Express, Windows Server 2008 R2+ / Windows 7+ (requer o .NET Framework 4.6.1) | Instalador `pkie-1.38.0-x64.msi`; pacotes x86 e zip na [página de instalação](https://docs.lacunasoftware.com/articles/pki-express/setup/windows) | 1 (1.38.0) | [cdn.lacunasoftware.com](https://cdn.lacunasoftware.com/pki-express/windows/pkie-1.38.0-x64.msi) |
| PKI Express, Linux (Ubuntu/Mint, RHEL, CentOS/Oracle Linux/Fedora) | `pkie-1.38.0.tar.gz` ([instalação](https://docs.lacunasoftware.com/articles/pki-express/setup)) | 1 (1.38.0) | [cdn.lacunasoftware.com](https://cdn.lacunasoftware.com/pki-express/linux/pkie-1.38.0.tar.gz) |
| Java | `com.lacunasoftware.pkiexpress:pki-express` | 1 (1.23.1) | [Maven Central](https://search.maven.org/artifact/com.lacunasoftware.pkiexpress/pki-express) |
| Node.js | `pki-express` | 1 (1.16.2) | [npm](https://www.npmjs.com/package/pki-express) |
| PHP 5.5+ | `lacuna/pki-express` | 1 (1.17.0) | [Packagist](https://packagist.org/packages/lacuna/pki-express) |
| Python | `pkiexpress` | 1 (1.10.4) | [PyPI](https://pypi.org/project/pkiexpress) |
| Ruby | `pki_express` | 1 (1.3.2) | [RubyGems](https://rubygems.org/gems/pki_express) |
No Linux, extraia o pacote em `/usr/share/pkie`, crie um link para `/usr/share/pkie/pkie` em `/usr/local/bin`, configure a pasta de logs e execute `sudo pkie activate LacunaPkiLicense.config`; as páginas de [Ubuntu](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-ubuntu), [RHEL](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-rhel) e [CentOS](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-centos) trazem os comandos. Para atualizar, execute o novo instalador (Windows) ou substitua o conteúdo de `/usr/share/pkie` pelo novo pacote (Linux).
## Configuração e autenticação
**Licença, sem chave de API.** O PKI Express precisa de um arquivo de licença, `LacunaPkiLicense.config`; peça à Lacuna uma licença de testes gratuita. Ative-a uma vez em cada máquina: com `pkie activate` no Linux ([ativação manual](https://docs.lacunasoftware.com/articles/pki-express/setup/manual-activation) se a ativação pela rede falhar) e com o *PKI Express Configuration Manager* (`pkiemgr.exe`, na pasta de instalação) no Windows. A sua aplicação não lê a licença, e não há URL de serviço: a biblioteca cliente executa o PKI Express instalado na mesma máquina.
As **configurações do servidor** são definidas com `pkie config --set name=value` (com privilégios de administrador); `pkie config --effective` mostra os valores em uso.
| Configuração | Chaves | Página |
|---|---|---|
| Pasta e nível mínimo de log | `logDir`, `logLevel` | [Logs](https://docs.lacunasoftware.com/articles/pki-express/config/log) |
| Pasta temporária (cache de LCRs e de certificados de AC) | `tempDir` | [Pasta temporária](https://docs.lacunasoftware.com/articles/pki-express/config/temp-dir) |
| Proxy | `proxyHost`, `proxyPort`, `proxyUsername`, `proxyPassword` | [Proxy](https://docs.lacunasoftware.com/articles/pki-express/config/proxy) |
| LAPP, para obter LCRs e certificados de AC por meio de `*.lacunasoftware.com` (o suporte também precisa autorizar o acesso) | `lappEnabled` | [LAPP](https://docs.lacunasoftware.com/articles/pki-express/config/lapp) |
| Chaves no Azure Key Vault | `azureAppId`, `azureAppSecret`, `azureKeyVaultEndpoint` | [Azure](https://docs.lacunasoftware.com/articles/pki-express/config/azure) |
| Certificados em nuvem (BirdID, NeoID, RemoteID, SafeID, VIDaaS e outros) | `trustServices::clientId`, `trustServices::clientSecret`, `trustServices::endpoint` | [Certificados em nuvem](https://docs.lacunasoftware.com/articles/pki-express/config/trust-services) |
| Chaves em tokens e HSMs PKCS#11 | `pkcs11LibraryPath` | [Histórico de versões, 1.38.0](https://docs.lacunasoftware.com/articles/pki-express/changelog#v1-38-0) |
TODO(docs): o `pkcs11LibraryPath` (PKI Express 1.38.0) não tem página de configuração; só o histórico de versões o cita, e nem as bibliotecas cliente nem um texto de ajuda publicado o descrevem.
**No seu código**, toda classe de operação herda de `PkiExpressOperator`, cujas configurações os exemplos aplicam num único helper, `Util.setPkiDefaults()`, chamado por todos os blocos abaixo:
- em que confiar: por padrão, só em certificados ICP-Brasil; adicione raízes com `addTrustedRootSync()`, ou a raiz de teste da Lacuna com `trustLacunaTestRoot` ([confiando em certificados que não sejam da ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-express/nodejs/trusting-in-custom-certificates));
- `timestampAuthority`, um `TimestampAuthority` para as políticas que exigem carimbo de tempo: os exemplos usam o do REST PKI, `https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310`, com um token de acesso do REST PKI (`setOAuthTokenAuthentication()`); usuário e senha, e SSL mútuo, também funcionam ([autoridade de carimbo de tempo](https://docs.lacunasoftware.com/articles/pki-express/php/configuring-timestamp-authority));
- `offline`, para executar sem acesso à rede.
Cada classe também aceita um `PkiExpressConfig` opcional (pasta do PKI Express, pasta temporária, pasta dos arquivos de transfer data): sem ele, as bibliotecas procuram o PKI Express nas pastas de instalação padrão do Windows e executam o `pkie` do `PATH` no Linux. No Linux, só a biblioteca de Java consegue executar o PKI Express 1.31.0 ou posterior a partir de uma pasta personalizada; veja [configurações da biblioteca cliente](https://docs.lacunasoftware.com/articles/pki-express/config#configurações-da-biblioteca-cliente).
## Operações principais
Os blocos são trechos dos exemplos em Node.js (`nodejs/expressmvc/routes/` no PkiSuiteSamples), a linguagem com mais exemplos do PKI Express. Os nomes das políticas vêm de `StandardSignaturePolicies` e diferem dos do Rest PKI: veja a [tabela de equivalência](https://docs.lacunasoftware.com/articles/pki-express/nodejs/convert-from-restpki).
TODO(docs): além do passo a passo em Java ([como funciona](https://docs.lacunasoftware.com/articles/pki-express/java/how-it-works)) e do mapa do projeto PHP ([exemplos em PHP](https://docs.lacunasoftware.com/articles/pki-express/php/plain)), nenhuma página documenta estas operações; o código abaixo vem dos exemplos.
### Assinar um PDF com o certificado do usuário
PAdES com a chave no computador do usuário, em duas etapas no servidor: `PadesSignatureStarter.start()` retorna o hash que o Web PKI assina no navegador (`signHash()`), e `SignatureFinisher.complete()` grava o PDF assinado. Passo a passo em Java: [como funciona](https://docs.lacunasoftware.com/articles/pki-express/java/how-it-works); aparência da assinatura: [representação visual](https://docs.lacunasoftware.com/articles/pki-express/nodejs/visual-rep).
```js
// POST /start: certContent is the user's certificate, read by Web PKI
const signatureStarter = new PadesSignatureStarter();
Util.setPkiDefaults(signatureStarter);
signatureStarter.signaturePolicy = StandardSignaturePolicies.PADES_BASIC_WITH_LTV;
signatureStarter.setPdfToSignFromPathSync(StorageMock.getDataPath(fileId));
signatureStarter.setCertificateFromBase64Sync(certContent);
signatureStarter.start().then((response) => { /* toSignHash, digestAlgorithm, transferFile */ });
// POST /complete: signature is the result of Web PKI's signHash()
const signatureFinisher = new SignatureFinisher();
Util.setPkiDefaults(signatureFinisher);
signatureFinisher.setFileToSignFromPathSync(StorageMock.getDataPath(fileId));
signatureFinisher.setTransferFileFromPathSync(transferFile);
signatureFinisher.signature = signature;
signatureFinisher.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
signatureFinisher.complete(true).then((result) => { /* signed PDF at outputFile */ });
```
### Assinar um PDF com uma chave no servidor
Uma etapa, sem navegador: `PadesSigner.sign()` assina com um arquivo PKCS #12 no servidor.
```js
const signer = new PadesSigner();
Util.setPkiDefaults(signer);
signer.signaturePolicy = StandardSignaturePolicies.PADES_BASIC_WITH_LTV;
await signer.setPdfToSignFromPath(StorageMock.getDataPath(fileId));
await signer.setPkcs12FromPath(StorageMock.getSamplePkcs12Path());
signer.certPassword = '1234';
signer.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
let signerCert = await signer.sign(true);
```
### Assinar um arquivo com CAdES
O `CadesSignatureStarter` segue o mesmo fluxo de início e conclusão, finalizado pelo `SignatureFinisher` (saída `.p7s`); se o arquivo já for um CMS, o PKI Express o coassina.
```js
const signatureStarter = new CadesSignatureStarter();
Util.setPkiDefaults(signatureStarter);
signatureStarter.signaturePolicy = StandardSignaturePolicies.PKI_BRAZIL_CADES_ADR_BASICA;
signatureStarter.setFileToSignFromPathSync(StorageMock.getDataPath(fileId));
signatureStarter.setCertificateFromBase64Sync(certContent);
signatureStarter.start().then((response) => { /* same as the PDF flow */ });
```
### Assinar um documento XML
`XmlSigner.sign()` assina com uma chave no servidor; com o certificado do usuário, use `XmlSignatureStarter` e `SignatureFinisher`, como no fluxo do PDF.
```js
const signer = new XmlSigner();
Util.setPkiDefaults(signer);
signer.signaturePolicy = StandardSignaturePolicies.XADES_BES;
await signer.setXmlToSignFromPath(StorageMock.getSampleDocPath(SampleDocs.SAMPLE_XML));
await signer.setPkcs12FromPath(StorageMock.getSamplePkcs12Path());
signer.certPassword = '1234';
signer.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
let signerCert = await signer.sign(true);
```
### Validar um PDF assinado
`PadesSignatureExplorer.open()` retorna as assinaturas do arquivo e, com `validate = true`, as valida; o `CadesSignatureExplorer` faz o mesmo para CMS/CAdES.
```js
const sigExplorer = new PadesSignatureExplorer();
Util.setPkiDefaults(sigExplorer);
sigExplorer.validate = true;
sigExplorer.setSignatureFileFromPathSync(filePath);
sigExplorer.signaturePolicy = StandardSignaturePolicies.PADES_BASIC;
sigExplorer.open().then((r) => res.render('open-pades-express', { signature: r }));
```
### Aplicar carimbo de tempo a um PDF
`PadesTimestamper.stamp()` aplica um carimbo de tempo a um PDF, usando a autoridade de carimbo de tempo do operador.
```js
const stamper = new PadesTimestamper();
Util.setPkiDefaults(stamper);
stamper.setPdfFromPath(pdfPath).then(() => {
stamper.setOutputFilePath(outputFilePath);
stamper.stamp().then(() => { /* timestamped PDF at outputFilePath */ });
});
```
### Gerar uma versão para impressão
Depois de abrir as assinaturas com o `PadesSignatureExplorer`, `PdfMarker.apply()` aplica ao PDF textos e imagens montados com o `PdfHelper`, como os nomes dos signatários e um código de verificação.
```js
const pdfMarker = new PdfMarker();
Util.setPkiDefaults(pdfMarker);
pdfMarker.setFileFromPathSync(pdfPath);
const pdf = new PdfHelper();
pdfMarker.marks.push(pdf.mark().onAllPages()
.onContainer(pdf.container().width(1).anchorRight(1).height(1).anchorBottom(1))
.addElement(pdf.imageElement().withOpacity(75).withImageContent(Util.getIcpBrasilLogoContent(), 'image/png')));
pdfMarker.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
pdfMarker.apply();
```
### Autenticar com um certificado
`Authentication.start()` retorna um nonce e um algoritmo de digest para o Web PKI assinar; `complete()` verifica a assinatura e valida o certificado.
```js
const auth = new Authentication();
Util.setPkiDefaults(auth);
auth.setNonce(nonce); // from auth.start(), signed in the browser by Web PKI
await auth.setCertificateFromBase64(certContent);
auth.setSignature(signature);
const result = await auth.complete();
if (result.validationResults.isValid()) { const userCert = result.certificate; }
```
## Erros
Toda operação do PKI Express termina com um código de retorno, `00 - Ok` ou um erro de `01` a `43`; assinaturas, autenticações e inspeções com validação também informam erros de validação, como `CertificateChainRootNotTrusted`. Lista completa: [códigos de retorno](https://docs.lacunasoftware.com/articles/pki-express/return-codes).
Em Node.js, uma operação que falha rejeita a sua promise com um `CommandError` cujo `code` é o código de retorno (`ValidationError` para o código 14, `InstallationNotFoundError` quando o PKI Express não é encontrado); os exemplos o repassam para `next(err)`. O PHP lança `CommandException` (`$e->returnCode`), o Ruby `PkiExpress::CommandError` (`e.code.exitstatus`); Java e Python lançam uma exceção simples, com a saída na mensagem: veja [códigos de retorno nas bibliotecas cliente](https://docs.lacunasoftware.com/articles/pki-express/return-codes#códigos-de-retorno-nas-bibliotecas-cliente).
| Erro | Significado | O que fazer |
|---|---|---|
| `17 - NotActivated` | O PKI Express não está ativado nesta máquina | Ative-o com a licença ([instalação](https://docs.lacunasoftware.com/articles/pki-express/setup)) |
| `14 - ValidationFailed` com `CertificateChainRootNotTrusted` | A raiz do certificado não é confiável; por padrão, só a ICP-Brasil é | Confie na raiz: `trustLacunaTestRoot` ou `addTrustedRootSync()` ([confiando em certificados que não sejam da ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-express/nodejs/trusting-in-custom-certificates)) |
| `CertificateRevocationStatusUnknown` | O PKI Express não conseguiu acessar as LCRs do certificado | Configure o [proxy](https://docs.lacunasoftware.com/articles/pki-express/config/proxy) ou o [LAPP](https://docs.lacunasoftware.com/articles/pki-express/config/lapp) |
| `32 - TimestampOptionsRequired` | Nenhuma autoridade de carimbo de tempo foi informada, e a política exige carimbo de tempo | Defina o `timestampAuthority` do operador ([autoridade de carimbo de tempo](https://docs.lacunasoftware.com/articles/pki-express/php/configuring-timestamp-authority)) |
## Certificados e credenciais de teste
- [Certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs): arquivos PKCS #12 da ICP de testes da Lacuna, senha `1234`; nunca confie nessa ICP em produção.
- O PKI Express só os aceita depois que você confia na raiz de teste da Lacuna: `trustLacunaTestRoot` ([Node.js](https://docs.lacunasoftware.com/articles/pki-express/nodejs/trusting-in-custom-certificates), [Java](https://docs.lacunasoftware.com/articles/pki-express/java/trusting-in-custom-certificates), [PHP](https://docs.lacunasoftware.com/articles/pki-express/php/trusting-in-custom-certificates)) ou `trust_lacuna_test_root` ([Python](https://docs.lacunasoftware.com/articles/pki-express/python/trusting-in-custom-certificates), [Ruby](https://docs.lacunasoftware.com/articles/pki-express/ruby/trusting-in-custom-certificates)).
- Licença: solicite uma [licença de testes gratuita](https://www.lacunasoftware.com/pt/home/purchase) e ative-a como em [Configuração e autenticação](https://docs.lacunasoftware.com/articles/pki-express/quick-reference#configuração-e-autenticação).
## Experimente
Rode as demos ou comece por um exemplo na sua linguagem:
**Demonstrações e exemplos**
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Demonstração: [Sign a PDF with your cloud certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-with-cloud-certificate)
- Demonstração: [Timestamp a PDF](https://try.lacunasoftware.com/pt/pki-suite/timestamp-a-pdf)
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Sign a PDF with PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_signature_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_signature_express_controller.rb)
- Sign multiple PDFs with PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchPadesSignatureExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/batch-pades-signature-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/batch-pades-signature-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/batch_pades_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/batch_pades_express_controller.rb)
- Sign a PDF with a server key using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesServerKeyExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-server-key-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-server-key-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_server_key_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_server_key_express_controller.rb)
- Sign a PDF with a cloud certificate (OAuth flow) using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesCloudOauthExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-cloud-oauth-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-cloud-oauth-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_cloud_oauth_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_cloud_oauth_express_controller.rb)
- Sign a PDF with a cloud certificate (password flow) using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesCloudPwdExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-cloud-pwd-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-cloud-pwd-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_cloud_pwd_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_cloud_pwd_express_controller.rb)
- Timestamp a PDF with PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/timestamp-pdf-express.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/timestamp_pdf_express.py)
- Sign a file with CAdES using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/CadesSignatureExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/cades-signature-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/cades_signature_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/cades_signature_express_controller.rb)
- Sign multiple files with CAdES using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchCadesSignatureExpressControler.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/batch-cades-signature-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/batch-cades-signature-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/batch_cades_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/batch_cades_express_controller.rb)
- Sign a file with CAdES and a server key using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/cades-server-key-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-server-key-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/cades_server_key_express.py)
- Sign a file with CAdES and a cloud certificate (OAuth flow) using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/CadesCloudOauthExpressController.java)
- Merge CMS/CAdES signatures using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/MergeCadesExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/merge-cades-express.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/merge_cades_express.py)
- Sign an XML document using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-signature-express.js)
- Sign an XML document with a server key using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-server-key-express.js)
- Sign an XML document with a cloud certificate (OAuth flow) using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-cloud-oauth-express.js)
- Sign a Brazilian NF-e using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-nfe-signature-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-nfe-signature-express/index.php)
- Sign a COD XML using PKI Express: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-cod-signature-express/index.php)
- Validate a signed PDF using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenPadesExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/open-pades-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-pades-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/open_pades_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/open_pades_express_controller.rb)
- Validate a CMS/CAdES signature using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenCadesExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/open-cades-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-cades-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/open_cades_express.py)
- Validate a signed XML using PKI Express: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-xml-express/index.php)
- Make a printer-friendly version of a signed PDF using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/printer-version-pades-express.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/printer_version_pades_express.py)
- Make a printer-friendly version of a CMS/CAdES signature using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/printer-version-cades-express.js)
- Authenticate with a digital certificate using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/authentication-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/authentication-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/authentication_express.py)
- Authenticate with a cloud certificate (OAuth flow) using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationCloudExpressController.java)
- Sign a medical prescription (Receita Simples) using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ReceitaSimplesExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/receita-simples-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/receita-simples-express/index.php) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/receita_simples_express_controller.rb)
# [PKI Express](https://docs.lacunasoftware.com/articles/pki-express)
O PKI Express é uma solução para realizar assinaturas digitais que pode ser utilizada
em virtualmente qualquer linguagem de programação. Além disso, o PKI Express pode
ser instalado tanto em servidores Windows quanto Linux.
Com o PKI Express, é possível:
* Criar assinaturas padrão CMS/CAdES
* Criar assinaturas padrão PDF/PAdES
* Criar assinaturas padrão XmlDSig/XAdES
> **Observação**
>
> Para usar o PKI Express, você precisará de uma licença. Por favor [fale conosco](https://www.lacunasoftware.com/pt/home/purchase) para obter uma licença para testes.
Próximos passos:
* [Instalação](https://docs.lacunasoftware.com/articles/pki-express/setup)
* [Usando em PHP](https://docs.lacunasoftware.com/articles/pki-express/php)
* [Usando em Java](https://docs.lacunasoftware.com/articles/pki-express/java)
* [Usando em Python](https://docs.lacunasoftware.com/articles/pki-express/python)
* [Usando em Node.js](https://docs.lacunasoftware.com/articles/pki-express/nodejs)
* [Usando em Ruby](https://docs.lacunasoftware.com/articles/pki-express/ruby)
# [Instalação do PKI Express](https://docs.lacunasoftware.com/articles/pki-express/setup)
Para instalar o PKI Express, escolha uma das plataformas suportadas:
* Linux
* [Ubuntu/Mint](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-ubuntu)
* [Red Hat Enterprise Linux (RHEL)](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-rhel)
* [CentOS/Oracle Linux/Fedora](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-centos)
* [Windows](https://docs.lacunasoftware.com/articles/pki-express/setup/windows)
As bibliotecas cliente procuram o PKI Express na pasta de instalação padrão. Se você instalá-lo em outra pasta, veja
[Configurações da biblioteca cliente](https://docs.lacunasoftware.com/articles/pki-express/config#configurações-da-biblioteca-cliente).
# [Instalação do PKI Express em CentOS, Oracle Linux ou Fedora](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-centos)
Para instalar o PKI Express no CentOS, Oracle Linux ou Fedora você precisará de uma licença de uso. Caso ainda não tenha uma licença, [solicite uma licença de teste](https://www.lacunasoftware.com/pt/home/purchase).
De posse do arquivo de licença (**LacunaPkiLicense.config**), siga os passos abaixo:
## Instalando o PKI Express
Baixe o pacote do PKI Express e extraia para a pasta de destino:
```sh
curl -O https://cdn.lacunasoftware.com/pki-express/linux/pkie-1.38.0.tar.gz
sudo mkdir /usr/share/pkie
sudo tar xzf pkie-1.38.0.tar.gz -C /usr/share/pkie
sudo chmod +x /usr/share/pkie/pkie
sudo ln -s /usr/share/pkie/pkie /usr/local/bin/pkie
```
Crie uma pasta para os logs e configure o PKI Express para utilizá-la:
```sh
sudo mkdir /var/log/pkie
sudo chmod 777 /var/log/pkie
sudo pkie config --set logDir=/var/log/pkie
```
Ative o PKI Express:
```sh
sudo pkie activate LacunaPkiLicense.config
```
> **Observação**
>
> Se a ativação automática falhar, siga as instruções no artigo [Ativação manual](https://docs.lacunasoftware.com/articles/pki-express/setup/manual-activation).
## Atualização
Para atualizar o PKI Express, apenas baixe o novo pacote e extraia para a pasta de destino:
```sh
curl -O https://cdn.lacunasoftware.com/pki-express/linux/pkie-1.38.0.tar.gz
sudo rm -R /usr/share/pkie/*
sudo tar xzf pkie-1.38.0.tar.gz -C /usr/share/pkie
sudo chmod -R a=r,a+X,u+w /usr/share/pkie
```
> **Observação**
>
> Caso você esteja atualizando o PKI Express a partir de uma versão 1.0.x, é preciso realizar as tarefas de configuração da pasta de logs e de ativação do PKI Express (veja procedimento acima)
# [Instalação do PKI Express em Red Hat Enterprise Linux](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-rhel)
Para instalar o PKI Express no Red Hat Enterprise Linux você precisará de uma licença de uso. Caso ainda não tenha uma licença, [solicite uma licença de teste](https://www.lacunasoftware.com/pt/home/purchase).
De posse do arquivo de licença (**LacunaPkiLicense.config**), siga os passos abaixo:
## Instalando o PKI Express
Baixe o pacote do PKI Express e extraia para a pasta de destino:
```sh
curl -O https://cdn.lacunasoftware.com/pki-express/linux/pkie-1.38.0.tar.gz
sudo mkdir /usr/share/pkie
sudo tar xzf pkie-1.38.0.tar.gz -C /usr/share/pkie
sudo chmod +x /usr/share/pkie/pkie
sudo ln -s /usr/share/pkie/pkie /usr/local/bin/pkie
```
Crie uma pasta para os logs e configure o PKI Express para utilizá-la:
```sh
sudo mkdir /var/log/pkie
sudo chmod 777 /var/log/pkie
sudo pkie config --set logDir=/var/log/pkie
```
Ative o PKI Express:
```sh
sudo pkie activate LacunaPkiLicense.config
```
> **Observação**
>
> Se a ativação automática falhar, siga as instruções no artigo [Ativação manual](https://docs.lacunasoftware.com/articles/pki-express/setup/manual-activation).
## Atualização
Para atualizar o PKI Express, apenas baixe o novo pacote e extraia para a pasta de destino:
```sh
curl -O https://cdn.lacunasoftware.com/pki-express/linux/pkie-1.38.0.tar.gz
sudo rm -R /usr/share/pkie/*
sudo tar xzf pkie-1.38.0.tar.gz -C /usr/share/pkie
sudo chmod -R a=r,a+X,u+w /usr/share/pkie
```
> **Observação**
>
> Caso você esteja atualizando o PKI Express a partir de uma versão 1.0.x, é preciso realizar as tarefas de configuração da pasta de logs e de ativação do PKI Express (veja procedimento acima)
# [Instalação do PKI Express em Ubuntu ou Mint](https://docs.lacunasoftware.com/articles/pki-express/setup/linux-ubuntu)
Para instalar o PKI Express em um Linux Ubuntu (*desktop* ou *server*) ou Linux Mint você precisará de uma licença de uso. Caso ainda não tenha uma licença, [solicite uma licença de teste](https://www.lacunasoftware.com/pt/home/purchase).
De posse do arquivo de licença (**LacunaPkiLicense.config**), siga os passos abaixo:
## Instalando o PKI Express
Baixe o pacote do PKI Express e extraia para a pasta de destino:
```sh
wget https://cdn.lacunasoftware.com/pki-express/linux/pkie-1.38.0.tar.gz
sudo mkdir /usr/share/pkie
sudo tar xzf pkie-1.38.0.tar.gz -C /usr/share/pkie
sudo chmod +x /usr/share/pkie/pkie
sudo ln -s /usr/share/pkie/pkie /usr/local/bin/pkie
```
Crie uma pasta para os logs e configure o PKI Express para utilizá-la:
```sh
sudo mkdir /var/log/pkie
sudo chmod 777 /var/log/pkie
sudo pkie config --set logDir=/var/log/pkie
```
Ative o PKI Express:
```sh
sudo pkie activate LacunaPkiLicense.config
```
> **Observação**
>
> Se a ativação automática falhar, siga as instruções no artigo [Ativação manual](https://docs.lacunasoftware.com/articles/pki-express/setup/manual-activation).
## Atualização
Para atualizar o PKI Express, apenas baixe o novo pacote e extraia para a pasta de destino:
```sh
wget https://cdn.lacunasoftware.com/pki-express/linux/pkie-1.38.0.tar.gz
sudo rm -R /usr/share/pkie/*
sudo tar xzf pkie-1.38.0.tar.gz -C /usr/share/pkie
sudo chmod -R a=r,a+X,u+w /usr/share/pkie
```
> **Observação**
>
> Caso você esteja atualizando o PKI Express a partir de uma versão 1.0.x, é preciso realizar as tarefas de configuração da pasta de logs e de ativação do PKI Express (veja procedimento acima)
# [Ativação manual do PKI Express](https://docs.lacunasoftware.com/articles/pki-express/setup/manual-activation)
Caso durante a instalação do [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) o procedimento de ativação falhe, siga os passos abaixo
para ativar o produto manualmente.
> **Observação**
>
> Essas instruções se aplicam apenas à instalação em ambientes Linux. Para ativar o PKI Express no Windows,
> tanto pela rede quanto maunalmente, utilize o utilitário *PKI Express Configuration Manager* (**pkiemgr.exe**)
Para ativar manualmente o PKI Express, primeiramente solicite o *código de ativação*:
```sh
pkie activate LacunaPkiLicense.config --request
```
Um arquivo chamado **pkie-activation-request.pem** será salvado na pasta atual. Entre em contato através da nossa [Central de Suporte](http://lacuna.help)
enviando o arquivo.
Você receberá de volta o arquivo de ativação (**pkie-activation.pem**). Copie o arquivo para a pasta
atual e execute:
```sh
sudo pkie activate
```
Ou, caso prefira, execute o comando passando o caminho do arquivo:
```sh
sudo pkie activate --file /path/to/pkie-activation.pem
```
# [Instalação do PKI Express em Windows](https://docs.lacunasoftware.com/articles/pki-express/setup/windows)
O PKI Express é compatível com os seguintes ambientes Windows:
* Server: Windows Server 2008 R2 ou superior
* Desktop: Windows 7 ou superior
Para instalar o PKI Express, baixe e execute o instalador:
**[PKI Express (64-bit) 1.38.0 installer](https://cdn.lacunasoftware.com/pki-express/windows/pkie-1.38.0-x64.msi) (recomendado)**
Após a instalação, execute o aplicativo *PKI Express Configuration Manager* (**arquivo `pkiemgr.exe` na pasta de instalação**) e siga as instruções
para ativar o PKI Express.
> **Observação**
>
> Só é possível encontrar o aplicativo navegando até a pasta, não há atalho no menu iniciar (isso será corrigido em uma versão futura)
## Downloads alternativos
Caso prefira, você pode optar por um dos downloads alternativos:
Arquivo | Descrição
------------------------------------------------------------------------------- | ------------------
**[pkie-1.38.0-x64.msi](https://cdn.lacunasoftware.com/pki-express/windows/pkie-1.38.0-x64.msi)** | **Instalador 64 bits**
[pkie-1.38.0-x86.msi](https://cdn.lacunasoftware.com/pki-express/windows/pkie-1.38.0-x86.msi) | Instalador 32 bits
[pkie-1.38.0-x64.zip](https://cdn.lacunasoftware.com/pki-express/windows/pkie-1.38.0-x64.zip) | Pacote zip 64 bits
[pkie-1.38.0-x86.zip](https://cdn.lacunasoftware.com/pki-express/windows/pkie-1.38.0-x86.zip) | Pacote zip 32 bits
Sempre que possível, escolha a versão 64 bits do PKI Express.
> **Observação**
>
> Para instalar o PKI Express em um ambiente de desenvolvimento para o qual você não tenha permissões de administrador,
> utilize um dos pacotes zip.
## .NET Framework
O PKI Express requer o .NET Framework 4.6.1, o qual provavelmente já encontra-se instalado em seu sistema. Entretanto,
caso durante a instalação você receba uma mensagem dizendo que o .NET Framework 4.6.1 não está instalado, utilize
um dos links abaixo:
* [Microsoft .NET Framework 4.6.1 (Web Installer)](https://www.microsoft.com/en-us/download/details.aspx?id=49981)
* [Microsoft .NET Framework 4.6.1 (Offline Installer)](https://www.microsoft.com/en-us/download/details.aspx?id=49982)
> **Observação**
>
> A instalação do .NET Framework 4.6.1 no Windows 7 e no Windows Server 2008 R2 exige que o Service Pack 1
> esteja instalado.
## Atualização
Para atualizar o PKI Express, apenas baixe a nova versão e execute o instalador.
# [Configuração - PKI Express](https://docs.lacunasoftware.com/articles/pki-express/config)
É possível alterar as seguintes configurações do PKI Express:
* [Logs](https://docs.lacunasoftware.com/articles/pki-express/config/log)
* [Pasta temporária](https://docs.lacunasoftware.com/articles/pki-express/config/temp-dir)
* [Proxy](https://docs.lacunasoftware.com/articles/pki-express/config/proxy)
* [Integração com o LAPP](https://docs.lacunasoftware.com/articles/pki-express/config/lapp)
* [Integração com o Azure](https://docs.lacunasoftware.com/articles/pki-express/config/azure)
* [Serviços de Certificado Digital em Nuvem](https://docs.lacunasoftware.com/articles/pki-express/config/trust-services)
A configuração do PKI Express é gerenciada através do comando `pkie config`. Veja as seções abaixo para saber mais sobre esse comando.
As bibliotecas cliente têm configurações próprias, como a pasta em que o PKI Express está instalado: veja
[Configurações da biblioteca cliente](https://docs.lacunasoftware.com/articles/pki-express/config#configurações-da-biblioteca-cliente).
## Comando *pkie config*
Para visualizar a sintaxe do comando:
```sh
pkie help config
```
Para visualizar as configurações atuais:
```sh
pkie config
```
Para alterar uma configuração:
```sh
pkie config --set name=value
```
Exemplo:
```sh
pkie config --set logLevel=3
```
## Configuração efetiva
O PKI Express pode eventualmente ignorar uma configuração inválida. Por exemplo, se a configuração
`tempDir` for setada para um caminho inválido ou uma pasta à qual o PKI Express não possui permissões
de escrita, a pasta temporária definida pelo ambiente será utilizada ao invés da pasta configurada.
Esse é o chamado valor *efetivo* da configuração, ou seja, o valor que o PKI Express está de fato
utilizando, que pode ser diferente do valor configurado manualmente.
Para diagnosticar problemas relacionados à configuração, pode ser útil verificar a configuração
efetiva do PKI Express:
```sh
pkie config --effective
```
## Configurações da biblioteca cliente
As configurações acima são do PKI Express e valem para todas as aplicações da máquina. As bibliotecas cliente têm
configurações próprias, na classe `PkiExpressConfig`, que você passa para o construtor de cada classe de operação
(`PadesSignatureStarter`, `SignatureFinisher`, `PadesSigner` etc.). Sem ela, a classe usa os padrões abaixo.
| Configuração | Padrão |
|---|---|
| Pasta do PKI Express | Windows: a pasta `Lacuna Software\PKI Express` que contém o `pkie.exe`, em `Program Files`, `Program Files (x86)` ou no `%LOCALAPPDATA%` do usuário. Linux: o comando `pkie` no `PATH`, onde as [páginas de instalação](https://docs.lacunasoftware.com/articles/pki-express/setup) o colocam |
| Pasta temporária | A pasta temporária do sistema (Ruby: uma pasta nova que a biblioteca cria dentro dela). Guarda os arquivos temporários que a biblioteca grava em cada operação |
| Pasta dos *transfer data files* | A pasta temporária. Guarda os *transfer data files*, que levam uma assinatura da etapa de início para a etapa de finalização; por isso, precisa ser a mesma nas duas etapas |
As três são argumentos do construtor, nessa ordem, e cada uma pode ficar vazia (`null`, `None` ou `nil`):
| Linguagem | Exemplo |
|---|---|
| Java | `new PadesSignatureStarter(new PkiExpressConfig("D:\\pkie", null, "D:\\pkie-transfer"))` (argumentos `String` ou `Path`) |
| Node.js | `new PadesSignatureStarter(new PkiExpressConfig('D:\\pkie', null, 'D:\\pkie-transfer'))` |
| PHP | `new PadesSignatureStarter(new PkiExpressConfig('D:\\pkie', null, 'D:\\pkie-transfer'))` |
| Python | `PadesSignatureStarter(PkiExpressConfig('D:\\pkie', None, 'D:\\pkie-transfer'))` |
| Ruby | `PkiExpress::PadesSignatureStarter.new(PkiExpress::PkiExpressConfig.new('D:\\pkie', nil, 'D:\\pkie-transfer'))` |
Se o PKI Express não estiver na pasta que você definiu ou, no Windows, em nenhuma das pastas padrão, a biblioteca lança
`InstallationNotFoundException` (Java, PHP) ou `InstallationNotFoundError` (Node.js, Python, Ruby). Se a pasta temporária
ou a pasta dos *transfer data files* que você passar não existir, as bibliotecas de Java, Node.js, PHP e Python usam o
padrão sem erro, enquanto a de Ruby lança `ArgumentError`.
> **Aviso: Pasta personalizada no Linux**
>
> Quando você define a pasta do PKI Express no Linux, as bibliotecas de Node.js, PHP, Python e Ruby executam
> `dotnet /pkie.dll`. Desde a versão 1.31.0, o pacote do PKI Express para Linux é um único executável `pkie`, sem
> `pkie.dll` ([histórico de versões](https://docs.lacunasoftware.com/articles/pki-express/changelog#v1-31-0)); com essas bibliotecas, deixe a pasta vazia e coloque o
> `pkie` no `PATH`. A biblioteca de Java executa `/pkie` quando a pasta não tem `pkie.dll`.
Para apagar os arquivos temporários de uma operação, chame `dispose()` (Java) ou `disposeSync()` (Node.js) depois que a
operação terminar, como faz o [exemplo de assinatura em Java](https://docs.lacunasoftware.com/articles/pki-express/java/how-it-works); as bibliotecas de PHP, Python e
Ruby os apagam quando o objeto é destruído ou coletado pelo garbage collector. Os *transfer data files* não são arquivos
temporários: a etapa de finalização precisa deles.
# [Integração com o Azure - PKI Express](https://docs.lacunasoftware.com/articles/pki-express/config/azure)
O PKI Express possibilita utilizar certificados e chaves armazenadas em Azure Key Vaults para assinar documentos.
Para utilizar certificados ou chaves armazenados em um key vault, você precisará dos seguintes parâmetros:
- **azureAppId**: campo **Application ID** de uma aplicação registrada no *Microsoft Entra ID*
- **azureAppSecret**: segredo da aplicação, gerado na área *Certificates & secrets* da aplicação
- **azureKeyVaultEndpoint**: campo `DnsName` do key vault, pode ser obtido na área *Overview* do vault
Para configurar o uso do Key Vault:
```sh
pkie config --set azureAppId=
pkie config --set azureAppSecret=
pkie config --set azureKeyVaultEndpoint=
```
# [Integração com o LAPP - PKI Express](https://docs.lacunasoftware.com/articles/pki-express/config/lapp)
Este artigo descreve como configurar o uso do LAPP - Proxy Provedor de Artefato para PKI.
O LAPP permite que recursos do PKI, como LCRs e certificados AC, sejam solicitados por meio de um único domínio (*.lacunasoftwre.com) que é então redirecionado para servidores mundiais para executar as solicitações simultaneamente. Ele simplifica o gerenciamento de redes e infraestruturas e cria conexões de alta disponibilidade.
O LAPP é recomendado quando:
- o servidor não tem acesso a rede;
- o firewall bloqueia o acesso a URL externas;
- a URL contendo as LCR e certificados AC sofrem instabilidades.
O comando abaixo ativa a integração, que por padrão está desativada.
```sh
pkie config --set lappEnabled=true
```
[!NOTE] Além da ativação é necessário contactar nosso suporte, em lacuna.help, para autorizarmos o acesso ao LAPP.
# [Logs - PKI Express](https://docs.lacunasoftware.com/articles/pki-express/config/log)
Este artigo descreve como configurar a pasta utilizada pelo PKI Express para escrever os
arquivos de log, bem como o nível dos logs escritos.
## Pasta de logs
Por padrão, o PKI Express escreve os arquivos de logs nas seguintes pastas:
* No Windows: `%TEMP%\pkie-`
* No Linux: `/var/log/pkie`
O nome do arquivo de log é `.log` ou um nome similar derivado deste, caso a funcionalidade de *file rolling* entre em ação para limitar o tamanho
do arquivo (cada arquivo de log é limitado a 4MB, e no máximo 64 arquivos são armazenados).
Para alterar a pasta de logs:
```sh
pkie config --set logDir=path
```
> **Observação**
>
> Os comandos citados neste artigo exigem privilégios de administrador. No Linux, pode ser necessário realizá-los com `sudo`
> ou, no Windows, em um *command prompt* de administrador.
A pasta **deve existir e estar liberada para escrita** tanto pelo seu usuário quanto pelo usuário utilizado para executar a aplicação web
(no Linux, geralmente esse usuário é o `www-data`).
## Nível mínimo
Os logs são categorizados em níveis crescentes de severidade:
* **Verbose**
* **Debug**
* **Information**
* **Warning** (nível mínimo padrão)
* **Error**
* **Fatal**
O **nível mínimo** (*minimum level*) define a menor severidade a partir da qual logs são escritos nos arquivos, sendo que logs de
severidades inferiores são descartados. O nível mínimo padrão para o PKI Express é o **Warning**.
Para alterar o nível mínimo:
```sh
pkie config --set logLevel=LEVEL
```
Por exemplo, para alterar o nível mínimo para *Information*:
```sh
pkie config --set logLevel=Information
```
Também são aceitos os seguintes números no valor da configuração:
* 0 = Verbose
* 1 = Debug
* 2 = Information
* 3 = Warning
* 4 = Error
* 5 = Fatal
Por exemplo, para alterar o nível mínimo para *Information*:
```sh
pkie config --set logLevel=2
```
# [Configuração de proxy - PKI Express](https://docs.lacunasoftware.com/articles/pki-express/config/proxy)
Para configurar o [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) para utilizar um proxy para fazer as chamadas web, execute o comando abaixo:
```sh
pkie config --set proxyHost=IP --set proxyPort=PORTA
```
> **Observação**
>
> Os comandos citados neste artigo exigem privilégios de administrador. No Linux, pode ser necessário realizá-los com `sudo`
> ou, no Windows, em um *command prompt* de administrador.
Caso o proxy exija autenticação com usuário e senha, faça também:
```sh
pkie config --set proxyUsername=USERNAME --set proxyPassword=PASSWORD
```
No Linux, certos caracteres na senha como `!` e `$` podem causar problemas. Nesse caso, utilize aspas simples no último argumento:
```sh
pkie config --set proxyUsername=USERNAME --set 'proxyPassword=PASSWORD'
```
Alternativamente, essas configurações podem ser controladas alterando as seguintes variáveis de ambiente:
* `pkie:proxyHost` ou `pkie__proxyHost`
* `pkie:proxyPort` ou `pkie__proxyPort`
* `pkie:proxyUsername` ou `pkie__proxyUsername`
* `pkie:proxyPassword` ou `pkie__proxyPassword`
> **Observação**
>
> Em ambientes Linux, os nomes das variáveis de ambiente são ***case sensitive***
# [Pasta temporária - PKI Express](https://docs.lacunasoftware.com/articles/pki-express/config/temp-dir)
O PKI Express utiliza uma pasta temporária para manter um *cache* local de Listas de Certificados Revogados (LCRs), certificados de Autoridades Certificadoras (ACs),
entre outros artefatos.
Por padrão, a pasta temporária utilizada é:
* No Windows: `%TEMP%` (variável de ambiente *TEMP*)
* No Linux: `/tmp`
Para alterar a pasta temporária:
```sh
pkie config --set tempDir=path
```
> **Observação**
>
> Os comandos citados neste artigo exigem privilégios de administrador. No Linux, pode ser necessário realizá-los com `sudo`
> ou, no Windows, em um *command prompt* de administrador.
A pasta **deve existir e estar liberada para escrita** pelo seu usuário e pelo usuário utilizado para executar a aplicação web (no Linux,
esse usuário geralmente é o `www-data`).
# [Serviços de Certificado Digital em Nuvem - PKI Express](https://docs.lacunasoftware.com/articles/pki-express/config/trust-services)
O PKI Express tem suporte a assinaturas com certificados digitais em nuvem, providos por prestadores de serviços em nuvem que seguem as especificações do ITI.
## Serviços pré-configurados
Por padrão, alguns serviços já são pré-configurados:
* BirdID (SOLUTI)
* NeoID (SERPRO)
* RemoteID (CERTISIGN)
* SafeID (SAFEWEB)
* VIDaaS (VALID)
Para utilizar esses serviços pré-configurados basta configurar o `clientId` e `clientSecret`:
```sh
pkie config --set trustServices::clientId=
pkie config --set trustServices::clientSecret=
```
Exemplo:
```sh
pkie config --set trustServices:BirdID:clientId="00000000000000"
pkie config --set trustServices:BirdID:clientSecret="xxx00xxXXXXxx0x"
```
## Serviços não pré-configurados
Para utilizar serviços não pré-configurados é necessário configurar também o `endpoint` do serviço:
```sh
pkie config --set trustServices::endpoint=
pkie config --set trustServices::clientId=
pkie config --set trustServices::clientSecret=
```
Exemplo:
```sh
pkie config --set trustServices:birdIdHml:endpoint="https://apihom.birdid.com.br/"
pkie config --set trustServices:birdIdHml:clientId="00000000000000"
pkie config --set trustServices:birdIdHml:clientSecret="xxx00xxXXXXxx0x"
```
Serviços não pré-configurados podem também ter outros parâmetros configurados opcionalmente:
* Provedor do serviço:
```sh
pkie config --set trustServices::provider=
```
* URL da logo do serviço:
```sh
pkie config --set trustServices::badgeUrl=
```
* Estratégia de tratamento em caso de erro, baseado nos serviços pré-configurados:
```sh
pkie config --set trustServices::protocolVariant=
```
## Timeout
Para configurar o timeout da busca por certificados nos provedores, utilize o comando abaixo:
```sh
pkie config --set trustServicesDiscoveryTimeout=
```
**Demonstrações e exemplos**
- Demonstração: [Sign a PDF with your cloud certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-with-cloud-certificate)
- Sign a PDF with a cloud certificate (OAuth flow) using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesCloudOauthExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-cloud-oauth-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-cloud-oauth-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_cloud_oauth_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_cloud_oauth_express_controller.rb)
- Sign a PDF with a cloud certificate (password flow) using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesCloudPwdExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-cloud-pwd-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-cloud-pwd-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_cloud_pwd_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_cloud_pwd_express_controller.rb)
- Sign a file with CAdES and a cloud certificate (OAuth flow) using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/CadesCloudOauthExpressController.java)
- Sign an XML document with a cloud certificate (OAuth flow) using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-cloud-oauth-express.js)
- Authenticate with a cloud certificate (OAuth flow) using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationCloudExpressController.java)
# [Usando o PKI Express em PHP](https://docs.lacunasoftware.com/articles/pki-express/php)
Este artigo mostra como usar o [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) em PHP. Para começar, escolha um dos projetos de exemplo disponíveis (organizado por framework):
- [Plain](https://docs.lacunasoftware.com/articles/pki-express/php/plain)
## Pacote de Composer
> **Observação**
>
> Este pacote só suporta PHP *5.5+*.
Para usar o PKI Express em PHP, você deve incluir o pacote composer [lacuna/pki-express](https://packagist.org/packages/lacuna/pki-express), adicionando a seguinte linha no seu arquivo `composer.json`:
```json
{
"require": {
"lacuna/pki-express": "^1.17.0"
}
}
```
Feito isso, execute o comando `composer install` para baixar o pacote e suas dependências (se você ainda não tem o Composer instalado, baixe-o [aqui](https://getcomposer.org/)).
O pacote é de código aberto, hospedado no [GitHub](https://github.com/LacunaSoftware/PkiExpressPhp). Você pode fazer um *fork* para fazer
customizações, e até mesmo submeter um *pull request*.
# [Configurando uma autoridade de carimbo de tempo](https://docs.lacunasoftware.com/articles/pki-express/php/configuring-timestamp-authority)
Quando se realiza uma assinatura com um política que exija uma integração com o carimbo de tempo, é preciso configurar uma autoridade de carimbo de tempo para usa classe [PkiExpressOperator](https://github.com/LacunaSoftware/PkiExpressPhp/blob/develop/src/PkiExpressOperator.php) ou **qualquer classe que herda dela**.
Use a classe [TimestampAuthority](https://github.com/LacunaSoftware/PkiExpressPhp/blob/develop/src/TimestampAuthority.php) para configurar a comunicação com o provedor de carimbo de tempo.
## Usando o provedor de carimbo de tempo do REST PKI
Para usar o provedor de carimbo de tempo do [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki), é necessário que configure as credenciais necessárias para a comunicação entre o PKI Express e o REST PKI. Para simplicarmos a integração, é possível utilizar o _access token_ para realizar essa comunicação (veja o [artigo](https://docs.lacunasoftware.com/articles/rest-pki/requesting-timestamps) para outros tipos de autenticação).
Primeiro, crie um conta no [site](https://pki.rest/) do REST PKI e **adquira um token de acesso** no _dashboad_. E, configure a aplicação com os seguintes parâmetros:
```php
$authority = new TimestampAuthority("https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310");
$authority->setOAuthTokenAuthentication('');
$operator->timestampAuthority = $authority;
```
### Exemplo (Assinatura de PDF com carimbo de tempo)
```php
// Performing a PAdES signature with timestamp
function configureTimestamp($operator) {
$authority = new TimestampAuthority("https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310");
$authority->setOAuthTokenAuthentication('');
$operator->timestampAuthority = $authority;
}
// Starting signature
$signatureStarter = new PadesSignatureStarter();
configureTimestamp($signatureStarter);
$signatureStarter->signaturePolicy(StandardSignaturePolicies::PADES_T); // A policy that requires timestamp
...
$signatureStarter->start();
// Completing signature
$signatureFinisher = new SignatureFinisher();
configureTimestamp($signatureFinisher);
...
$signatureFinisher->complete();
```
Leia mais: [Solicitando carimbos de tempo ao Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/requesting-timestamps)
## Usando outros provedores de carimbo de tempo
Para usar um provedor diferente, é preciso ter a URL para este provedor e qual método de autenticação ele utiliza para esta operação.
Atualmente, a class `TimestampAuthority` só suporta três tipos de autenticação:
- Usuário e Senha;
- Token de Acesso; e
- SSL mútua.
Primeiro, instancie a class `TimestampAuthority`com a URL do provedor de carimbo de tempo.
```php
$authority = new TimestampAuthority("");
```
E para cada tipo de autenticação, é preciso utilizar um método diferente da class `TimestampAuthority`:
### Autenticação com Usuário e Senha
Use o método `setBasicAuthentication()` para configurar a autenticação com o usuário e senha:
```php
$authority->setBasicAuthentication('', '');
```
### Autenticação com Token de Acesso
Use o método `setOAuthTokenAuthentication()` para configurar a autenticação com token de acesso:
```php
$authority->setOAuthTokenAuthentication('');
```
### Autenticação com SSL Mútua
Use o método `setSSLAuthentication()` com o **thumbprint do certificado** para configurar a autenticação com SSL mútua.
```php
$authority->setSSLAuthentication('');
```
> **Observação**
>
> Aqui, o PKI Express irá olhar no _storage_ de certificado da aplicação para olhar o certificado deseja. Ele irá filtrar os certificados existentes por seus thumbprints.
# [Como converter do REST PKI para o PKI Express em PHP](https://docs.lacunasoftware.com/articles/pki-express/php/convert-from-restpki)
Este artigo explica as alterações necessárias para converter uma aplicação PHP utilizando [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki) para uma aplicação PHP que utilize [PKI Express](https://docs.lacunasoftware.com/articles/pki-express).
## Equivalência de políticas
Ao migrar sua aplicação PHP do REST PKI para o PKI Express, é importante se atentar aos nomes das políticas usadas na aplicação.
Na tabela a seguir, é possível encontrar as equivalências entre os nomes das políticas em ambas bibliotecas. Na primeira coluna temos os nomes das políticas na biblioteca REST PKI, e na segunda coluna temos os nomes das mesmas políticas na biblioteca PKI Express.
| REST PKI | PKI Express |
|-----------------------------------------|--------------------------------------------------------------------------|
| PADES_BASIC | PADES_BASIC_WITH_LTV |
| PADES_BASIC_WITH_ICPBR_CERTS | PADES_BASIC_WITH_LTV |
| PADES_BASIC_WITHOUT_LTV | PADES_BASIC |
| PADES_T_WITH_ICPBR_CERTS | PADES_T |
| PADES_ICPBR_ADR_BASICA | Política ainda não existente (a constante equivalente seria "adrb-ltv") |
| PADES_ICPBR_ADR_TEMPO | Política ainda não existente (a constante equivalente seria "adrt") |
| PADES_ADOBE_READER | Política ainda não existente |
| CADES_BES | CADES_BES_WITH_REVOCATION_VALUES |
| CADES_BES_WITH_SIGNING_TIME_AND_NO_CRLS | CADES_BES |
| CADES_ICPBR_ADR_BASICA_WITHOUT_CRLS | PKI_BRAZIL_CADES_ADR_BASICA |
| CADES_ICPBR_ADR_BASICA | PKI_BRAZIL_CADES_ADR_BASICA_WITH_REVOCATION_VALUES |
| CADES_ICPBR_ADR_TEMPO | PKI_BRAZIL_CADES_ADR_TEMPO |
| CADES_ICPBR_ADR_COMPLETA | PKI_BRAZIL_CADES_ADR_COMPLETA |
| XML_XADES_BES | XADES_BES |
| XML_DSIG_BASIC | XML_DSIG_BASIC |
| XML_ICPBR_ADR_BASICA | PKI_BRAZIL_XML_ADR_BASICA |
| XML_ICPBR_ADR_TEMPO | PKI_BRAZIL_XML_ADR_TEMPO |
| XML_ICPBR_NFE_PADRAO_NACIONAL | NFE_PADRAO_NACIONAL |
| XML_COD_SHA1 | Política ainda não existente (a constante equivalente seria "cod-sha1") |
| XML_COD_SHA256 | Política ainda não existente (a constante equivalente seria "cod-sh256") |
> **Aviso**
>
> A política de validação PADES_ADOBE_READER não apresenta equivalência no PKI Express.
# [Projeto de Exemplos PHP Plain](https://docs.lacunasoftware.com/articles/pki-express/php/plain)
O projeto de exemplos demonstra o uso do [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) junto com o componente [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) em um projeto utilizando **PHP 5.5+ plain** (incluindo 7.x). Ele encontra-se hospedado no GitHub:
[https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/php](https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/php)
> **Observação**
>
> Se você utiliza uma versão anterior do PHP, por favor [fale conosco](https://www.lacunasoftware.com/pt/home/purchase).
## Executando o Projeto
1. [Instale o PKI Express](https://docs.lacunasoftware.com/articles/pki-express/setup)
1. [Baixe o projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou faça um *clone* do repositório: [https://github.com/LacunaSoftware/PkiSuiteSamples.git](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Entre na pasta `/php/plain`
1. Em um terminal, execute o comando `composer install` para baixar as dependências (se você não possuir o Composer instalado, baixe-o [aqui](https://getcomposer.org/))
1. Execute o project ao executar o comando `composer start`
1. Acesse a URL [http://localhost:8000](http://localhost:8000)
## Mapa do Projeto
Essa seção lista onde encontrar as partes relevante de cada exemplo de *feature* no projeto:
### Autenticação com Certificados Digitais
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/authentication-express/index.php)
- JavaScript: [authentication-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/authentication-form.js)
- [complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/authentication-express/complete.php)
### Assinatura PAdES com Arquivo do Servidor
Depois de escolher um dos arquivos do servidor disponíveis (que é implementado simplificadamente para propósito de demonstração em [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/server-files/index.php) e [action.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/server-files/action.php)), o fluxo é feito pelos seguintes arquivos:
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-express/index.php)
- JavaScript: [signature-start-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-start-form.js)
- [start.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-express/start.php)
- JavaScript: [signature-complete-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-complete-form.js)
- [complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-express/complete.php)
### Assinatura PAdES com Arquivo Enviado Pelo Usuário Por Upload
Depois que o upload do arquivo (que é implementado simplificadamente para propósito de demonstração em [upload.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/upload.php)) é feito, o fluxo é feito pelos seguintes arquivos:
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-express/index.php)
- JavaScript: [signature-start-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-start-form.js)
- [start.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-express/start.php)
- JavaScript: [signature-complete-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-complete-form.js)
- [complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-express/complete.php)
### Co-Assinatura PAdES
Depois de escolher um dos arquivos **já assinados** do servidor disponíveis (que é implementado simplificadamente para propósito de demonstração em [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/server-files/index.php) e [action.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/server-files/action.php)), o fluxo é feito pelos seguintes arquivos:
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-express/index.php)
- JavaScript: [signature-start-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-start-form.js)
- [start.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-express/start.php)
- JavaScript: [signature-complete-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-complete-form.js)
- [complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-express/complete.php)
### Assinatura PAdES com Chave no Servidor
Depois que o upload do arquivo (que é implementado simplificadamente para propósito de demonstração em [upload.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/upload.php)) é feito, o fluxo é feito pelos seguintes arquivos:
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-server-key-express/index.php)
### Lote de Assinaturas PAdES
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/batch-pades-signature-rest/index.php)
- JavaScript: [batch-signature-rest-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/batch-signature-rest-form.js)
- Ajax handlers: [start.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/batch-pades-signature-rest/start.php) and [complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/batch-pades-signature-rest/complete.php)
### Abrir/Validar uma Assinatura PAdES Existente
Depois que o upload do arquivo (que é implementado simplificadamente para propósito de demonstração em [upload.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/upload.php)) é feito, o fluxo é feito pelos seguintes arquivos:
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-pades-rest/index.php)
### Versão para Impressão de uma Assinatura PAdES
Não está disponível nesse projeto ainda.
### Assinatura CAdES com Arquivo do Servidor
Depois de escolher um dos arquivos do servidor disponíveis (que é implementado simplificadamente para propósito de demonstração em [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/server-files/index.php) e [action.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/server-files/action.php)), o fluxo é feito pelos seguintes arquivos:
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/index.php)
- JavaScript: [signature-start-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-start-form.js)
- [start.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/start.php)
- JavaScript: [signature-complete-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-complete-form.js)
- [complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/complete.php)
### Assinatura CAdES com Arquivo Enviado Pelo Usuário Por Upload
Depois que o upload do arquivo (que é implementado simplificadamente para propósito de demonstração em [upload.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/upload.php)) é feito, o fluxo é feito pelos seguintes arquivos:
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/index.php)
- JavaScript: [signature-start-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-start-form.js)
- [start.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/start.php)
- JavaScript: [signature-complete-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-complete-form.js)
- [complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/complete.php)
### Co-Assinatura CAdES
Depois de escolher um dos arquivos **já assinados** do servidor disponíveis (que é implementado simplificadamente para propósito de demonstração em [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/server-files/index.php) e [action.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/server-files/action.php)), o fluxo é feito pelos seguintes arquivos:
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/index.php)
- JavaScript: [signature-start-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-start-form.js)
- [start.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/start.php)
- JavaScript: [signature-complete-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-complete-form.js)
- [complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/complete.php)
### Assinatura CAdES com Chave no Servidor
Depois que o upload do arquivo (que é implementado simplificadamente para propósito de demonstração em [upload.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/upload.php)) é feito, o fluxo é feito pelos seguintes arquivos:
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-server-key-express/index.php)
### Lote de Assinaturas CAdES
Não está disponível nesse projeto ainda.
### Abrir/Validar uma Assinatura CAdES Existente
Depois que o upload do arquivo (que é implementado simplificadamente para propósito de demonstração em [upload.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/upload.php)) é feito, o fluxo é feito pelos seguintes arquivos:
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-cades-express/index.php)
### Versão para Impressão de uma Assinatura CAdES
Não está disponível nesse projeto ainda.
### Pacote de Assinatura de uma Assinatura PAdES
Não está disponível nesse projeto ainda.
### Assinatura XML do Documento Inteiro
Não está disponível nesse projeto ainda.
### Assinatura de NFe
- [index.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-nfe-signature-express/index.php)
- JavaScript: [signature-start-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-start-form.js)
- [start.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-nfe-signature-express/start.php)
- JavaScript: [signature-complete-form.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/scripts/signature-complete-form.js)
- [complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-nfe-signature-express/complete.php)
### Assinatura de Documento COD
Não está disponível nesse projeto ainda.
### Abrir/Validar Assinaturas em Documento XML Existente
Não está disponível nesse projeto ainda.
**Demonstrações e exemplos**
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Sign a COD XML using PKI Express: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-cod-signature-express/index.php)
- Validate a signed XML using PKI Express: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-xml-express/index.php)
# [Confiando em certificados que não sejam da ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-express/php/trusting-in-custom-certificates)
Por padrão, o PKI Express só permite operações realizadas com certificados brasileiros válidos, emitidos no âmbito da ICP-Brasil. Para usar outro tipo de certificado, é preciso configurar a classe [PkiExpressOperator](https://github.com/LacunaSoftware/PkiExpressPhp/blob/develop/src/PkiExpressOperator.php) ou **qualquer classe que herde dela**.
## Usando os certificados de teste da Lacuna
Se você estiver testando sua aplicação com nossos certificados fictícios (veja [certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs)), defina o campo `trustLacunaTestRoot` como `true` para confiar nesses certificados:
```php
$operator->trustLacunaTestRoot = true;
```
## Usando um certificado personalizado
Se você estiver usando outros tipos de certificado, informe à classe do operador o certificado da AC raiz de cada um deles.
```php
$operator->addTrustedRoot('/path/to/root1.cer');
$operator->addTrustedRoot('/path/to/root2.cer');
$operator->addTrustedRoot('/path/to/root3.cer');
```
# [Representação Visual](https://docs.lacunasoftware.com/articles/pki-express/php/visual-rep)
A representação visual na assinatura de PDF é a inclusão de um texto ou imagem contendo informações sobre a assinatura que está interna ao documento. Dependendo do software de visualização do PDF, esta representação pode fazer uma ligação com os dados e propriedades da assinatura e do signatário.
**Cada representação visual é sempre relativa a apenas uma assinatura no PDF e cada assinatura pode ter até uma representação visual.**
Nas seções abaixo exploraremos as opções, parâmetros e customizações na definição de uma representação visual para assinaturas em PDF.
## Elementos da Representação Visual
O array de representação visual possui 3 parâmetros que podem ser definidos: `position`, `text` e `image`. A lista abaixo introduz os elementos da representação visual.
* Posição
A posição do retângulo da representação visual da assinatura pode ser definida de forma automática ou manual.
* Posicionamento Automático
O posicionamento automático é indicado para casos em que pode-se definir uma região (container) em uma página do PDF que irá receber a representação das assinaturas. Este modo irá organizar as assinaturas em série dentro da região especificada. A figura abaixo exemplifica o comportamento do posicionamento automático. O retângulo vermelho representa o container definido no qual as representações visuais serão posicionadas automaticamente em série.

* Posicionamento Manual
O posicionamento manual é indicado para os casos em que cada representação das assinaturas serão inseridas em posições arbitrárias, por exemplo a posição e a página serem informados pelo usuário.

* Texto
As informações textuais a serem inseridas na representação visual da assinatura são customizáveis pelo parâmetro `text`.
* Imagem
É possível incluir uma imagem de fundo na representação visual da assinatura, podendo também customizar sua opacidade e seu alinhamento dentro do retângulo da representação visual. Estas opções são definidas pelo parâmetro `image`.
## Posicionamento da representação da assinatura
No posicionamento, define-se a página na qual a representação da assinatura será inserida, sua posição na página e a unidade de medida utilizada para informar a posição.
A página é especificada pelo parâmetro `pageNumber`.
Números negativos são contados a partir do final do documento (-1 significa última página, -2 penúltima, etc).
Pode-se usar o valor especial 0 (zero), que especifica que a representação da assinatura deve ser colocada em uma nova página em branco adicionada ao final do documento.
A unidade de medida é especificada pelo parâmetro `measurementUnits`. Por padrão o seu valor é `"centimeters"`, mas também pode ser `"pdfPoints"`.
### Posicionamento Manual
A posição do retângulo de assinatura em relação à página é especificada pelo parâmetro `manual`. Um retângulo pode ter os parâmetros `top`, `bottom`, `left`, `right`, `width` e `height` conforme mostrado na figura abaixo.

Por exemplo, para especificar que a representação visual deve ser inserida na última página do documento, tendo tamanho
7cm de largura e 3cm de altura, ficando a 2.5cm das margens esquerda e inferior, conforme a imagem abaixo, escreveríamos
o seguinte código:

```php
$signer = new PadesSigner();
...
$visual = array(
'position' => array(
'pageNumber' => -1, // Define inserção na última página do documento
'manual' => array(
'width' => 7, // Largura = 7cm
'height' => 3, // Altura = 3cm
'left' => 2.50, // Distância da margem esquerda = 2.50cm
'bottom' => 2.50 // Distância da margem inferior = 2.50cm
)
),
'text' => array(...),
'image' => array(...)
);
$signer->setVisualRepresentation($visual);
```
Para mais informações sobre como definir a posição da representação da assinatura, veja o artigo
[Definindo um container](https://docs.lacunasoftware.com/articles/pki-express/php/visual-rep/containers).
### Posicionamento Automático
No posicionamento automático, ao invés de se definir o posicionamento da representação visual em relação à página, define-se um
container que irá receber as representações das sucessivas assinaturas realizadas no documento e o tamanho de cada
retângulo de assinatura dentro do container.
O container de assinaturas é definido pelo parâmetro `container`.
De maneira similar à especificação do retângulo de assinatura no posicionamento manual, o container pode ter os parâmetros
`top`, `bottom`, `left`, `right`, `width` e `height`.
Já o tamanho de cada retângulo de assintura (`width` e `height`) é definido pelo parâmetro `signatureRectangleSize`.
A propriedade `rowSpacing` define o espaçamento entre as "linhas" das representações visuais.
Quando não há mais espaço em uma linha, as representações são inseridas em uma nova linha respeitando o `rowSpacing` passado.
Por padrão, as assinaturas são inseridas da esquerda para direita, de cima para baixo. No entanto, esse comportamento pode ser customizado com os parâmetros `horizontalDirection` (`"rightToLeft"` ou `"leftToRight"`) e `verticalDirection` (`"topDown"` ou `"bottomUp"`).
Por exemplo, para especificar que as representações visuais devem ser inseridas sucessivamente na última página do documento dentro de um container espaçado 3cm das bordas laterais, 2cm da borda inferior e 8cm da borda superior, sendo que cada retângulo de assinatura deve ter 7cm de largura por 3cm de altura, conforme ilustrado na imagem abaixo, escreveríamos o seguinte código:

```php
$signer = new PadesSigner();
...
$visual = array(
'position' => array(
'pageNumber' => -1, // Define inserção na última página do documento
'auto' => array(
'container' => array(
'left' => 3.00, // Distância da margem esquerda = 3.00cm
'right' => 3.00, // Distância da margem direita = 3.00cm
'bottom' => 2.00, // Distância da margem inferior = 2.00cm
'top' => 8.00 // Distância da margem superior = 8.00cm
),
'signatureRectangleSize' => array(
'height' => 3, // Altura = 3cm
'width' => 7 // Largura = 7cm
),
'rowSpacing' => 1.00 // 1.00cm de espaçamento entre linhas
)
),
'text' => array(...),
'image' => array(...)
);
$signer->setVisualRepresentation($visual);
```
> **Observação**
>
> O container que definimos acima tem altura e largura variáveis. Essa é uma das muitas possibilidades de especificação do container de assinaturas. Para mais informações, veja o artigo [Definindo um container](https://docs.lacunasoftware.com/articles/pki-express/php/visual-rep/containers).
As assinaturas realizadas com o código acima terão o resultado esperado conforme a imagem abaixo:

## Posicionamento de texto e imagem
* Texto
Interno ao retângulo da representação visual da assinatura, o PKI Express permite a customização do alinhamento, posição e conteúdo do texto a ser inserido. O posicionamento do texto pode ser controlado através da definição de um `container`. Se nenhum container for definido, o texto preencherá todo o retângulo de assinatura.
O alinhamento do texto dentro do container é customizável pelo parâmetro `horizontalAlign` (`left` ou `right`). O parâmetro `fontSize` permite customizar o tamanho da fonte utilizada no texto da representação.
Para inserir a data e hora da assinatura é necessario colocar o parâmetro `includeSigningTime` como verdadeiro. E a formatação da data e hora da assinatura é customizável pelo parâmetro `signingTimeFormat`.
O parâmetro `text` suporta o uso de *tags* que o PKI Express substitui pelas informações contidas no certificado.
* *Tags* genéricas:
Tag | Valor
----------------- | -----------
`{{name}}` | Valor mais apropriado para ser utilizado como nome do assinante (mais recomendado para esse propósito do que o `{{subject_cn}}`)
`{{national_id}}` | Valor mais apropriado para ser utilizado como número de identidade nacional do assinante. Para certificados ICP-Brasil, corresponde ao CPF do assinante. Para certificados italianos, corresponde ao *codice fiscale* do assinante.
`{{email}}` | Endereço de email do assinante
`{{subject_cn}}` | O campo **Common Name** (CN) do titular (*subject name*) do certificado do signatário
`{{issuer_cn}}` | O campo **Common Name** (CN) do emissor (*issuer name*) do certificado do signatário
* *Tags* específicas para certificados ICP-Brasil:
Tag | Valor
----------------------- | -----------
`{{br_cpf}}` | CPF do titular (para certificados de PJ, CPF do responsável pelo certificado)
`{{br_cpf_formatted}}` | Mesmo que `{{br_cpf}}` porém formatado como **000.000.000-00**
`{{br_cnpj}}` | CNPJ da empresa
`{{br_cnpj_formatted}}` | Mesmo que `{{br_cnpj}}` porém formatado como **00.000.000/0000-00**
`{{br_responsavel}}` | Nome do titular (para certificados de PJ, nome do responsável pelo certificado)
`{{br_company}}` | Nome da empresa
`{{br_oab_numero}}` | Número de Inscrição junto a Seccional da OAB (sem zeros à esquerda)
`{{br_oab_uf}}` | Sigla do Estado da Seccional da OAB
`{{br_rg_numero}}` | Número do RG do titular/responsável (sem zeros à esquerda)
`{{br_rg_emissor}}` | Órgão emissor do RG do titular/responsável
`{{br_rg_uf}}` | UF do órgão emissor do RG do titular/responsável
* *Tags* suportadas para manter a retrocompatibilidade:
Tag | Tag equivalente
---------------------- | ---------------
`{{signerName}}` | `{{name}}`
`{{signerEmail}}` | `{{email}}`
`{{signerNationalId}}` | `{{national_id}}`
`{{issuerCommonName}}` | `{{issuer_cn}}`
`{{br_oab_numbero}}` | `{{br_oab_numero}}`
Como exemplo, definiremos um posicionamento do texto relativo ao retângulo de assinatura conforme a figura abaixo.

```php
$visual = array(
'text' => array( // Texto da representação visual
'text' => "Signed by {{name}}", // Inclui o nome do signatário
'container' => array ( // Define container do texto
'left' => 0,
'top' => 0,
'right' => 1.5,
'bottom' => 0.5
),
'includeSigningTime' => true, // Inclui a data da assinatura
'signingTimeFormat' => "dd/MM/yy H:mm:ss zzz", // Define a formatação da data
'fontSize' => 10.0 // Define tamanho da fonte do texto
),
'position' => array(...),
'image' => array(...)
);
```
* Imagem
Também é possível definir uma imagem de fundo para o retângulo de assinatura e customizar sua opacidade e alinhamento.
```php
$signer = new PadesSigner();
...
// Cria uma referencia para o arquivo stamp. E posteriormente esse arquivo pode ser
// referenciado como "fref://{alias}" no campo "url" da representação visual
$signer->addFileReference('stamp', getPdfStampPath());
$visual = array(
'text' => array(...),
'position' => array(...),
'image' => array(
'resource' => array(
'url' => 'fref://stamp', // Utiliza imagem_de_fundo referenciada como 'stamp'
),
'horizontalAlign' => 'right', // Alinha a imagem horizontamente na direita
'verticalAlign' => 'center', // Alinha a imagem verticalmente no centro
)
);
$signer->setVisualRepresentation($visual);
```
* É possível remover a representação visual:
```php
$signatureStarter->suppressDefaultVisualRepresentation = true;
```
## Veja também
* [Exemplos completos de representação visual](https://docs.lacunasoftware.com/articles/pki-express/php/visual-rep/samples)
# [Usando o PKI Express em Java](https://docs.lacunasoftware.com/articles/pki-express/java)
Este artigo mostra como usar o [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) em Java
## Projeto de exemplos
O projeto de exemplos demonstra o uso do PKI Express junto com o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto compatível com **Java 7+** (incluindo 8 e 9) utilizando o framework [Spring MVC](http://spring.io/).
Ele encontra-se hospedado no GitHub:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/java/springmvc
> **Observação**
>
> Se você utiliza uma versão anterior do Java, por favor [fale conosco](https://www.lacunasoftware.com/pt/home/purchase).
### Executando o projeto
Para executar o projeto, é necessário ter o Java Development Kit instalado. Você pode usar as seguintes ferramentas:
**Usando Gradle**
1. [Instale o PKI Express](https://docs.lacunasoftware.com/articles/pki-express/setup)
1. [Baixe o projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou faça um *clone* do [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Em um terminal, navegue até a pasta `java/springmvc` e execute o comando `gradlew bootRun` (no Linux, `./gradlew bootRun`). Se você estiver em um ambiente Windows, você pode simplesmente dar um duplo-clique no arquivo `Run-Sample.bat`
1. Quando aparecer a mensagem "Started Application in x.xxx seconds" (a porcentagem exibida não chegará a 100%), abra um navegador e visite a URL http://localhost:60695/
> **Observação**
>
> Se você estiver no Linux, pode ser necessário adicionar a permissão de execução ao arquivo *gradlew* executando o comando `chmod +x gradlew`.
**Usando Maven**
1. [Instale o PKI Express](https://docs.lacunasoftware.com/articles/pki-express/setup)
1. [Baixe o projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou faça um *clone* do [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Em um terminal, navegue até a pasta `java/springmvc` e execute o comando `mvn spring-boot:run`. Para executar esse comando, é necessário
ter o Apache Maven instalado.
1. Quando aparecer a mensagem "Started Application in x.xxx seconds" (a porcentagem exibida não chegará a 100%), abra um navegador e visite a URL http://localhost:60695/
## Pacote Maven
O uso do PKI Express em Java deve ser feito com o pacote Maven [pki-express](https://search.maven.org/artifact/com.lacunasoftware.pkiexpress/pki-express),
publicado no repositório *Maven Central*.
Se o seu projeto é compilado com o **Maven**, adicione os seguintes trechos ao seu arquivo `pom.xml`:
```xml
...
com.lacunasoftware.pkiexpress
pki-express
1.23.1
...
```
Se o seu projeto é compilado com o **Gradle**, adicione os seguintes trechos ao seu arquivo `build.gradle`:
```groovy
repositories {
mavenCentral()
}
dependencies {
implementation 'com.lacunasoftware.pkiexpress:pki-express:1.23.1'
}
```
O pacote é de código aberto, hospedado no [GitHub](https://github.com/LacunaSoftware/PkiExpressJava). Você pode fazer um *fork* para fazer
customizações, e até mesmo submeter um *pull request*.
## Veja também
* [Como funciona a assinatura com PKI Express em Java](https://docs.lacunasoftware.com/articles/pki-express/java/how-it-works)
**Demonstrações e exemplos**
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Sign a PDF with PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
- Sign multiple PDFs with PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchPadesSignatureExpressController.java)
- Sign a PDF with a server key using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesServerKeyExpressController.java)
- Sign a file with CAdES using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/CadesSignatureExpressController.java)
- Sign multiple files with CAdES using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchCadesSignatureExpressControler.java)
- Merge CMS/CAdES signatures using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/MergeCadesExpressController.java)
- Validate a signed PDF using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenPadesExpressController.java)
- Validate a CMS/CAdES signature using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenCadesExpressController.java)
- Authenticate with a digital certificate using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationExpressController.java)
- Sign a medical prescription (Receita Simples) using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/ReceitaSimplesExpressController.java)
# [Gerando códigos alfanuméricos em Java](https://docs.lacunasoftware.com/articles/pki-express/java/alpha-codes)
Ao gerar uma versão para impressão de um arquivo assinado, é necessário gerar um *código de verificação* a ser
incluido no documento para que um terceiro que porventura receba o documento impresso possa visitar o seu site e fornecer o
código, obtendo assim a versão assinada digitalmente (que é a que efetivamente tem validade jurídica):

No passado, nós províamos o código-fonte para geração desse código de verificação como parte dos exemplos, a ser copiado
para o código da sua aplicação. Por exemplo:
```cs
public static class Util {
// ...
public static string GenerateVerificationCode() {
// String with exactly 32 letters and numbers to be used on the codes.
const string Alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
// Allocate a byte array large enough to receive the necessary entropy
var bytes = new byte[(int)Math.Ceiling(VerificationCodeSize * 5 / 8.0)];
// ...
return sb.ToString();
}
// ...
}
```
Entretanto, como o código de verificação desempenha um papel crucial na proteção do acesso aos seus documentos, nós
agora oferecemos a classe `AlphaCode` para fazer a geração dos códigos.
## Atualizando sua aplicação para usar a classe *AlphaCode*
> **Aviso**
>
> A lógica de geração de códigos de verificação passou por uma auditoria minuciosa e recebeu importantes melhorias.
> Por isso, recomendamos que você atualize a sua aplicação para utilizar a classe *AlphaCode* ao invés de utilizar
> o código-fonte antigo.
Você provavelmente copiou anteriormente os métodos (agora obsoletos) `generateVerificationCode`, `formatVerificationCode` e `parseVerificationCode`
dos exemplos para o seu código. Para atualizar a sua aplicação:
1. Atualize o pacote de Maven *com.lacunasoftware.pkiexpress* para a versão **1.10.0** ou superior
1. Substitua a implementação desses métodos no seu código por chamadas aos métodos da classe *AlphaCode*:
```java
public static String generateVerificationCode() {
return AlphaCode.generate();
}
public static String formatVerificationCode(String code) {
return AlphaCode.format(code);
}
public static String parseVerificationCode(String formattedCode) {
return AlphaCode.parse(formattedCode);
}
```
## Princípios de projeto
A classe *AlphaCode* gera códigos alfanuméricos feitos para serem lidos por pessoas seguindo os princípios abaixo:
1. Os códigos devem de fácil leitura
1. Os códigos devem poder ser facilmente digitados com baixo risco de confundir caracteres similares como `O` e `0`
1. Os códigos devem ter relativa alta entropia com relação ao tamanho do código (alto número de possíveis códigos relativo ao tamanho
do código, permitindo ao desenvolvedor escolher um código de tamanho relativamente pequeno)
Para melhorar a legibilidade, são utilizadas apenas letras maiúsculas e não são utilizados os caracteres `O`, `0`, `1` e `I`. A entropia
por caractere é relativamente alta, se comparada com um código hexadecimal: como existem 32 possíveis caracteres, cada caractere contribui
com 5 bits à entropia total do código gerado (25% a mais do que num código hexadecimal), o que resulta em códigos menores para uma dada entropia mínima.
Por exemplo, para gerar um código com 80 bits de entropia (2^80 possíveis códigos):
* Em hexadecimal, seria necessário um código de 20 caracteres, ex: `90A0-F20F-5883-8D55-AD31`
* Com a classe *AlphaCode*, o código teria apenas 16 caracteres, ex: `FFWC-RHC5-9NLF-VM42`
## Veja também
* [Gerando códigos alfanuméricos em Java (Rest PKI)](https://docs.lacunasoftware.com/articles/rest-pki/java/alpha-codes)
* [Usando o PKI Express em Java](https://docs.lacunasoftware.com/articles/pki-express/java)
**Demonstrações e exemplos**
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
# [Como converter do REST PKI para o PKI Express em Java](https://docs.lacunasoftware.com/articles/pki-express/java/convert-from-restpki)
Este artigo explica as alterações necessárias para converter uma aplicação Java utilizando [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki) para uma aplicação Java que utilize [PKI Express](https://docs.lacunasoftware.com/articles/pki-express).
## Equivalência de políticas
Ao migrar sua aplicação Java do REST PKI para o PKI Express, é importante se atentar aos nomes das políticas usadas na aplicação.
Na tabela a seguir, é possível encontrar as equivalências entre os nomes das políticas em ambas bibliotecas. Na primeira coluna temos os nomes das políticas na biblioteca REST PKI, e na segunda coluna temos os nomes das mesmas políticas na biblioteca PKI Express.
| REST PKI | PKI Express |
|----------------------------------|-------------------------------------------------------------------------|
| PadesBasic | PadesBasicWithLTV |
| PadesBasicWithPkiBrazilCerts | PadesBasicWithLTV |
| PadesTWithPkiBrazilCerts | PadesT |
| PkiBrazilPadesAdrBasica | Política ainda não existente (a constante equivalente seria "adrb-ltv") |
| PkiBrazilPadesAdrTempo | Política ainda não existente (a constante equivalente seria "adrt") |
| CadesBes | CadesBesWithRevocationValues |
| CadesBesWithSigningTimeAndNoCrls | CadesBes |
| PkiBrazilAdrBasica | PkiBrazilCadesAdrBasicaWithRevocationValues |
| PkiBrazilAdrTempo | PkiBrazilCadesAdrTempo |
| PkiBrazilAdrCompleta | PkiBrazilCadesAdrCompleta |
| XadesBasic | XadesBes |
| XmlDSigBasic | XmlDSigBasic |
| PkiBrazilXadesAdrBasica | PkiBrazilXadesAdrBasica |
| PkiBrazilXadesAdrTempo | PkiBrazilXadesAdrTempo |
| NFePadraoNacional | NFePadraoNacional |
| CodSha1 | CodWithSHA1 |
| CodSha256 | CodWithSHA256 |
# [Como funciona a assinatura com PKI Express em Java](https://docs.lacunasoftware.com/articles/pki-express/java/how-it-works)
Este artigo explica como funciona o processo de assinatura digital realizado no projeto de exemplos do [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) em Java.
## Principais arquivos de código
* Controller [PadesSignatureExpressController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
* View [pades-signature-express/index](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/index.html)
* Módulo de JavaScript [signature-start-form](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)
* View [pades-signature-express/start](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/start.html)
* Módulo de JavaScript [signature-complete-form](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)
* View [pades-signature-express/signature-info](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/signature-info.html)
## Processo de assinatura
### 1. Usuário vai para a página de assinatura
Nesse momento, o usuário visita a URL `/pades-signature-express?fileId={fileToSign}` (`GET`). O controller `PadesSignatureExpressController` é o responsável por atender à requisição, mais especificamente no método `get()`:
```java
@Controller
@RequestMapping("/pades-signature-express")
public class PadesSignatureExpressController {
/**
* GET /pades-signature-express?fileId={fileToSign}
*
* This action simply renders the page.
*/
@GetMapping
public String get(@RequestParam(value = "fileId") String fileToSign) throws IOException {
// Verify if the fileToSign exists.
if (fileToSign == null || !StorageMock.exists(fileToSign)) {
throw new FileNotFoundException();
}
// Render the signature page (templates/pades-signature-express/index.html)
return "pades-signature-express/index";
}
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
Nesse momento, apenas verificamos que o arquivo a ser assinado existe e renderizamos a página de assinatura, a view `pades-signature-express/index`.
> **Observação**
>
> O parâmetro `fileId` e a classe `StorageMock` são meramente detalhes do exemplo: representam a forma como a sua aplicação armazena os arquivos.
Ao final do carregamento da página, passamos referências aos elementos da página para o módulo de JavaScript `signatureStartForm`:
```js
$(document).ready(function () {
// Once the page is ready, we call the init() function on the javascript code
// (see signature-start-form.js).
signatureStartForm.init({
form: $('#signForm'), // The form that should be submitted when the operation is complete.
certificateSelect: $('#certificateSelect'), // The element (combo box) to list the certificates.
refreshButton: $('#refreshButton'), // The "refresh" button.
signButton: $('#signButton'), // The button that initiates the operation.
certThumbField: $('#certThumbField'), // The "certThumb" field.
certContentField: $('#certContentField') // The "certContent" field.
});
});
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/index.html)
O módulo `signatureStartForm` bloqueia a tela e dispara a inicialização do Web PKI, passando a função `loadCertificates()` como callback para quando a inicialização
tiver sido concluída, e a função `onWebPkiError()` como `defaultFail`, o callback de erro de qualquer operação posterior do Web PKI:
```js
// ----------------------------------------------------------------------------------------------
// Initializes the signature form.
// ----------------------------------------------------------------------------------------------
function init(fe) {
// Receive form parameters received as arguments.
formElements = fe;
// Wireup of buttons clicks.
formElements.signButton.click(startSignature);
formElements.refreshButton.click(refresh);
// Block the UI while we get things ready.
$.blockUI({ message: 'Initializing ...' });
// Call the init() method on the LacunaWebPKI object, passing a callback for when the
// component is ready to be used and another to be called when an error occurs on any of the
// subsequent operations. For more information, see:
// https://docs.lacunasoftware.com/en-us/articles/web-pki/get-started.html#coding-the-first-lines
// https://webpki.lacunasoftware.com/Help/classes/LacunaWebPKI.html#method_init
pki.init({
ready: loadCertificates, // As soon as the component is ready we'll load the certificates.
defaultFail: onWebPkiError // Generic error callback (see function declaration below).
});
}
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)
Na função `loadCertificates()`, disparamos o carregamento dos certificados disponíveis no computador do usuário, populando o elemento `` da página:
```js
// ----------------------------------------------------------------------------------------------
// Function that loads the certificates, either on startup or when the user clicks the "Refresh"
// button. At this point, the UI is already blocked.
// ----------------------------------------------------------------------------------------------
function loadCertificates() {
// Call the listCertificates() method to list the user's certificates.
pki.listCertificates({
// ID of the element to be populated with the certificates:
selectId: formElements.certificateSelect.attr('id'),
// Function that will be called to get the text that should be displayed for each option:
selectOptionFormatter: function (cert) {
var s = cert.subjectName + ' (issued by ' + cert.issuerName + ')';
if (new Date() > cert.validityEnd) {
s = '[EXPIRED] ' + s;
}
return s;
}
}).success(function () {
// Once the certificates have been listed, unblock the UI.
$.unblockUI();
});
}
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)
### 2. Usuário clica no botão **Sign File**
Anteriormente, o módulo de JavaScript `signatureStartForm` registrou a função de JavaScript `startSignature()` para ser chamada quando o botão **Sign File** é clicado:
```js
// Wireup of buttons clicks.
formElements.signButton.click(startSignature);
formElements.refreshButton.click(refresh);
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)
> **Observação**
>
> Note que é fundamental que o botão na página tenha `type="button"`, e não `type="submit"`, caso contrário ocorreria um *postback* imediatamente ao clicar no botão.
Portanto, quando o usuário clica no botão **Sign File**, a função `startSignature()` do `signatureStartForm` é invocada:
```js
// ----------------------------------------------------------------------------------------------
// Function called when the user clicks the "Sign File" button.
// ----------------------------------------------------------------------------------------------
function startSignature() {
// Block the UI while we perform the signature.
$.blockUI({ message: 'Starting signature ...' });
// Get the thumbprint of the selected certificate.
var selectedCertThumbprint = formElements.certificateSelect.val();
// Get certificate content to be passed to "start" action after the form submission.
pki.readCertificate(selectedCertThumbprint).success(/* ... */);
}
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)
Nessa função, após bloquear a página, obtemos o *thumbprint* do certificado selecionado, que será necessário em outro momento do processo de assinatura. Invocamos então a função
`readCertificate()` do Web PKI para obter a codificação do certificado escolhido (parte pública do certificado, o ".cer", sem a chave privada):
```js
// Get certificate content to be passed to "start" action after the form submission.
pki.readCertificate(selectedCertThumbprint).success(// ...
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)
Uma vez lido o certificado, preenchemos os campos ocultos da página com o *thumbprint* e o certificado e fazemos o *postback*:
```js
/* ... */.success(function (certEncoded) {
// Fill fields needed on the next steps of the signature.
formElements.certThumbField.val(selectedCertThumbprint);
formElements.certContentField.val(certEncoded);
// Submit the form.
formElements.form.submit();
});
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)
### 3. Início da assinatura no *backend*
No backend, o controller `PadesSignatureExpressController` novamente é invocado para tratar o postback feito a partir do frontend com o certificado escolhido para realizar a assinatura, feito para a URL `/pades-signature-express/start` com verbo `POST`. O método `postStart()` é responsável por tratar a requisição:
```java
/**
* POST /pades-signature-express/start?fileId={fileToSign}
*
* This action receives the form submission from the signature page. It will perform a PAdES
* signature in three steps using PKI Express and Web PKI.
*/
@PostMapping("/start")
public String postStart(
@RequestParam(value = "fileId") String fileToSign,
@RequestParam String certThumb,
@RequestParam String certContent,
Model model
) throws IOException {
// ...
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
Nesse ponto, iniciamos a assinatura usando a classe `PadesSignatureStarter`, passando a política de assinatura, o arquivo a ser assinado e a codificação do certificado escolhido para assinatura:
```java
// Get an instance of the PadesSignatureStarter class, responsible for receiving
// the signature elements and start the signature process.
PadesSignatureStarter signatureStarter = new PadesSignatureStarter();
// Set PKI default options (see Util.java)
Util.setPkiDefaults(signatureStarter);
// Set signature policy.
signatureStarter.setSignaturePolicy(StandardSignaturePolicies.PadesBasicWithLTV);
// Set PDF to be signed.
signatureStarter.setPdfToSign(StorageMock.getDataPath(fileToSign));
// Set Base64-encoded certificate's content to signature starter.
signatureStarter.setCertificateBase64(certContent);
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
A classe é criada sem argumentos e, por isso, usa o `PkiExpressConfig` padrão: o PKI Express na pasta de instalação padrão e a pasta temporária do sistema (veja [configurações da biblioteca cliente](https://docs.lacunasoftware.com/articles/pki-express/config#configurações-da-biblioteca-cliente)). O `Util.setPkiDefaults()` aplica o que todas as operações do exemplo têm em comum: as raízes confiáveis, incluindo a raiz de teste da Lacuna quando configurada ([confiando em certificados que não sejam da ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-express/java/trusting-in-custom-certificates)), o modo offline e a autoridade de carimbo de tempo.
Em seguida, configuramos a aparência da assinatura:
```java
// Set visual representation. We provide a Java class that represents the visual
// representation model.
signatureStarter.setVisualRepresentation(PadesVisualElements.getVisualRepresentation());
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
> **Observação**
>
> Para mais detalhes sobre as opções de configuração da aparência da assinatura, veja a classe
> [PadesVisualElements](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/util/express/PadesVisualElements.java)
> e o artigo [representação visual de assinaturas PAdES](https://docs.lacunasoftware.com/articles/pki-express/java/visual-rep).
Por fim, concluímos a etapa inicial, obtendo os dados necessários para realizar o algoritmo de assinatura digital, e apagamos os arquivos temporários que o *starter* criou:
```java
// Start the signature process. Receive as response a SignatureStartResult instance
// containing the following fields:
// - toSignHash: The hash to be signed.
// - digestAlgorithm: The digest algorithm that will inform the Web PKI component
// to compute the signature.
// - transferFile: A temporary file to be passed to "complete" step.
SignatureStartResult result = signatureStarter.start();
// If you want to delete the temporary files created by this step use the method
// dispose(). This method MUST be called after the start() method, because it
// deletes some files needed by the method.
signatureStarter.dispose();
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
Um ponto importante: a classe `PadesSignatureStarter` salva um arquivo chamado *transfer data file* contendo informações que precisam ser recuperadas
mais à frente no processo de assinatura. Por padrão, esse arquivo é armazenado na pasta temporária do sistema, porém outra pasta pode ser configurada com o `PkiExpressConfig`
([configurações da biblioteca cliente](https://docs.lacunasoftware.com/articles/pki-express/config#configurações-da-biblioteca-cliente)). O `dispose()` não o apaga, já que a etapa de finalização precisa dele.
O método `SignatureStartResult.getTransferFile()` retorna o nome do arquivo, o qual, junto com os demais campos do resultado, é preenchido em campos ocultos na página:
```java
// Render the fields received form start() method as hidden fields to be used on
// the javascript or on the "complete" step.
model.addAttribute("certThumb", certThumb);
model.addAttribute("certContent", certContent);
model.addAttribute("toSignHash", result.getToSignHash());
model.addAttribute("transferFileId", result.getTransferFile());
model.addAttribute("digestAlgorithm", result.getDigestAlgorithm());
return "pades-signature-express/start";
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
> **Observação**
>
> Preocupações de segurança são tomadas na geração do nome desse arquivo, de modo que é impossível um usuário adivinhar o *transfer data file* de um
> processo de assinatura de outro usuário. Além disso, apenas o nome do arquivo é enviado para a página, não o caminho completo, de modo que também não é
> possível a um usuário induzir a aplicação web a ler um arquivo em uma pasta arbitrária.
O controller retorna a view `pades-signature-express/start`, que contém esses campos ocultos.
### 4. Realização do algoritmo de assinatura digital no *frontend*
Uma vez carregada a página, a função `init()` do `signatureCompleteForm` é invocada com referências aos campos ocultos:
```js
$(document).ready(function () {
// Once the page is ready, we call the init() function on the javascript code
// (see signature-complete-form.js).
signatureCompleteForm.init({
form: $('#signForm'), // The form that should be submitted when the operation is complete.
certThumbField: $('#certThumbField'), // The "certThumb" field.
toSignHashField: $('#toSignHashField'), // The "toSignHash" field.
digestAlgorithmField: $('#digestAlgorithmField'), // The "digestAlgorithm" field.
signatureField: $('#signatureField') // The "signature" field.
});
});
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/start.html)
Bloqueamos então a página e invocamos novamente a inicialização do Web PKI, porém dessa vez passando a função `sign()` como callback:
```js
// ----------------------------------------------------------------------------------------------
// Initializes the signature form.
// ----------------------------------------------------------------------------------------------
function init(fe) {
// Receive form parameters received as arguments.
formElements = fe;
// Block the UI while we get things ready.
$.blockUI({ message: 'Signing ...' });
// Call the init() method on the LacunaWebPKI object, passing a callback for when the
// component is ready to be used and another to be called when an error occurs on any of the
// subsequent operations. For more information, see:
// https://docs.lacunasoftware.com/en-us/articles/web-pki/get-started.html#coding-the-first-lines
// https://webpki.lacunasoftware.com/Help/classes/LacunaWebPKI.html#method_init
pki.init({
ready: sign, // As soon as the component is ready we'll perform the signature.
defaultFail: onWebPkiError // Generic error callback defined below.
});
}
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)
Na função `sign()`, invocamos o Web PKI para realizar o algoritmo de assinatura digital com a chave privada do certificado, usando como dados de entrada os dados retornados pelo PKI Express no backend:
```js
// ----------------------------------------------------------------------------------------------
// Function that performs the signature on startup. At this point, the UI is already blocked.
// ----------------------------------------------------------------------------------------------
function sign() {
// Call signHash() on the Web PKI component passing the "toSignHash", the digest algorithm
// and the certificate selected by the user.
pki.signHash({
thumbprint: formElements.certThumbField.val(),
hash: formElements.toSignHashField.val(),
digestAlgorithm: formElements.digestAlgorithmField.val()
}).success(function (signature) {
// Fill the "signature" field, needed on server-side to complete the signature.
formElements.signatureField.val(signature);
// Submit the form.
formElements.form.submit();
});
}
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)
Uma vez finalizado o algoritmo de assinatura, preenchemos o resultado em um campo oculto na página e fazemos um novo *postback* para o backend:
```js
/* ... */.success(function (signature) {
// Fill the "signature" field, needed on server-side to complete the signature.
formElements.signatureField.val(signature);
// Submit the form.
formElements.form.submit();
});
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)
### 5. Finalização da assinatura no *backend*
No backend, o controller `PadesSignatureExpressController` novamente é invocado para tratar o postback feito a partir do frontend com o resultado do algoritmo de assinatura digital, feito para a URL `/pades-signature-express/complete` com verbo `POST`. O método `postComplete()` é responsável por tratar a requisição:
```java
@PostMapping("/complete")
public String postComplete(
@RequestParam(value = "fileId") String fileToSign,
@RequestParam String transferFileId,
@RequestParam String signature,
Model model
) throws IOException {
// ...
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
Obtemos uma instância da classe `SignatureFinisher` e fornecemos o arquivo sendo assinado, o nome do *transfer data file* e o resultado do algoritmo de assinatura feito no frontend com o Web PKI:
```java
// Get an instance of the SignatureFinisher class, responsible for completing the
// signature process.
SignatureFinisher signatureFinisher = new SignatureFinisher();
// Set PKI default options (see Util.java)
Util.setPkiDefaults(signatureFinisher);
// Set PDF to be signed. It's the same file we used on "start" step.
signatureFinisher.setFileToSign(StorageMock.getDataPath(fileToSign));
// Set transfer file.
signatureFinisher.setTransferFilePath(transferFileId);
// Set the signature value.
signatureFinisher.setSignature(signature);
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
> **Observação**
>
> Na versão atual do pacote `pki-express` (1.23.1), o `setTransferFilePath(String)` está obsoleto: use o `setTransferFileId()`, que recebe o mesmo valor.
Geramos um novo nome de arquivo para receber o PDF assinado:
```java
// Generate path for output file and add to signature finisher.
String outputFile = StorageMock.generateFileId("pdf");
signatureFinisher.setOutputFilePath(StorageMock.getDataPath(outputFile));
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
Finalizamos o processo de assinatura e, novamente, apagamos os arquivos temporários que o *finisher* criou:
```java
// Complete the signature process.
signatureFinisher.complete();
// If you want to delete the temporary files created by this step, use the method
// dispose(). This method MUST be called after the complete() method, because it
// deletes some files needed by the method.
signatureFinisher.dispose();
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
Após esse comando, o arquivo assinado encontra-se salvo no caminho de saída definido acima. Fica, então, a critério da sua aplicação mover esse arquivo para o local correto, seja em banco de dados, sistema de arquivos ou serviço de storage. O exemplo renderiza a view `pades-signature-express/signature-info`, com um link para baixar o arquivo assinado:
```java
// Render the signature page (templates/pades-signature-express/signature-info.html).
model.addAttribute("signedPdf", outputFile);
return "pades-signature-express/signature-info";
```
[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
**Demonstrações e exemplos**
- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Sign a PDF with PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
- Sign a file with CAdES using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/CadesSignatureExpressController.java)
# [Confiando em certificados que não sejam da ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-express/java/trusting-in-custom-certificates)
Por padrão, o PKI Express só permite operações realizadas com certificados brasileiros válidos, emitidos no âmbito da ICP-Brasil. Para usar outro tipo de certificado, é preciso configurar a classe [PkiExpressOperator](https://github.com/LacunaSoftware/PkiExpressJava/blob/master/src/main/java/com/lacunasoftware/pkiexpress/PkiExpressOperator.java) ou **qualquer classe que herde dela**.
## Usando os certificados de teste da Lacuna
Se você estiver testando sua aplicação com nossos certificados fictícios (veja [certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs)), defina o campo `trustLacunaTestRoot` como `true` para confiar nesses certificados:
```java
operator.setTrustLacunaTestRoot(true);
```
## Usando um certificado personalizado
Se você estiver usando outros tipos de certificado, informe à classe do operador o certificado da AC raiz de cada um deles.
```java
operator.addTrustedRoot("/path/to/root1.cer");
operator.addTrustedRoot("/path/to/root2.cer");
operator.addTrustedRoot("/path/to/root3.cer");
```
# [Representação Visual](https://docs.lacunasoftware.com/articles/pki-express/java/visual-rep)
A representação visual na assinatura de PDF é a inclusão de um texto ou imagem contendo informações sobre a assinatura que está interna ao documento. Dependendo do software de visualização do PDF, esta representação pode fazer uma ligação com os dados e propriedades da assinatura e do signatário.
**Cada representação visual é sempre relativa a apenas uma assinatura no PDF e cada assinatura pode ter até uma representação visual.**
Nas seções abaixo exploraremos as opções, parâmetros e customizações na definição de uma representação visual para assinaturas em PDF.
## Elementos da Representação Visual
O array de representação visual possui 3 parâmetros que podem ser definidos: `position`, `text` e `image`. A lista abaixo introduz os elementos da representação visual.
* Posição
A posição do retângulo da representação visual da assinatura pode ser definida de forma automática ou manual.
* Posicionamento Automático
O posicionamento automático é indicado para casos em que pode-se definir uma região (container) em uma página do PDF que irá receber a representação das assinaturas. Este modo irá organizar as assinaturas em série dentro da região especificada. A figura abaixo exemplifica o comportamento do posicionamento automático. O retângulo vermelho representa o container definido no qual as representações visuais serão posicionadas automaticamente em série.

* Posicionamento Manual
O posicionamento manual é indicado para os casos em que cada representação das assinaturas serão inseridas em posições arbitrárias, por exemplo a posição e a página serem informados pelo usuário.

* Texto
As informações textuais a serem inseridas na representação visual da assinatura são customizáveis pelo parâmetro `text`.
* Imagem
É possível incluir uma imagem de fundo na representação visual da assinatura, podendo também customizar sua opacidade e seu alinhamento dentro do retângulo da representação visual. Estas opções são definidas pelo parâmetro `image`.
## Posicionamento da representação da assinatura
No posicionamento, define-se a página na qual a representação da assinatura será inserida, sua posição na página e a unidade de medida utilizada para informar a posição.
A página é especificada pelo parâmetro `pageNumber`.
Números negativos são contados a partir do final do documento (-1 significa última página, -2 penúltima, etc).
Pode-se usar o valor especial 0 (zero), que especifica que a representação da assinatura deve ser colocada em uma nova página em branco adicionada ao final do documento.
A unidade de medida é especificada pelo parâmetro `measurementUnits`. Por padrão o seu valor é `"centimeters"`, mas também pode ser `"pdfPoints"`.
### Posicionamento Manual
A posição do retângulo de assinatura em relação à página é especificada pelo parâmetro `manual`. Um retângulo pode ter os parâmetros `top`, `bottom`, `left`, `right`, `width` e `height` conforme mostrado na figura abaixo.

Por exemplo, para especificar que a representação visual deve ser inserida na última página do documento, tendo tamanho
7cm de largura e 3cm de altura, ficando a 2.5cm das margens esquerda e inferior, conforme a imagem abaixo, escreveríamos
o seguinte código:

```java
PadesSigner signer = new PadesSigner();
...
PadesVisualRepresentation vr = new PadesVisualRepresentation();
PadesVisualManualPositioning position = new PadesVisualManualPositioning();
position.setPageNumber(-1); // Define inserção na última página do documento
PadesVisualRectangle signatureRectangle = new PadesVisualRectangle();
signatureRectangle.setWidth(7); // Largura = 7cm
signatureRectangle.setHeight(3); // Altura = 3cm
signatureRectangle.setLeft(2.5); // Distância da margem esquerda = 2.50cm
signatureRectangle.setBottom(2.5); // Distância da margem inferior = 2.50cm
position.setSignatureRectangle(signatureRectangle);
vr.setText(...);
vr.setImage(...);
signer.setVisualRepresentation(vr);
```
Para mais informações sobre como definir a posição da representação da assinatura, veja o artigo
[Definindo um container](https://docs.lacunasoftware.com/articles/pki-express/java/visual-rep/containers).
### Posicionamento Automático
No posicionamento automático, ao invés de se definir o posicionamento da representação visual em relação à página, define-se um
container que irá receber as representações das sucessivas assinaturas realizadas no documento e o tamanho de cada
retângulo de assinatura dentro do container.
O container de assinaturas é definido pelo parâmetro `container`.
De maneira similar à especificação do retângulo de assinatura no posicionamento manual, o container pode ter os parâmetros
`top`, `bottom`, `left`, `right`, `width` e `height`.
Já o tamanho de cada retângulo de assintura (`width` e `height`) é definido pelo parâmetro `signatureRectangleSize`.
A propriedade `rowSpacing` define o espaçamento entre as "linhas" das representações visuais.
Quando não há mais espaço em uma linha, as representações são inseridas em uma nova linha respeitando o `rowSpacing` passado.
Por padrão, as assinaturas são inseridas da esquerda para direita, de cima para baixo. No entanto, esse comportamento pode ser customizado com os parâmetros `horizontalDirection` (`"rightToLeft"` ou `"leftToRight"`) e `verticalDirection` (`"topDown"` ou `"bottomUp"`).
Por exemplo, para especificar que as representações visuais devem ser inseridas sucessivamente na última página do documento dentro de um container espaçado 3cm das bordas laterais, 2cm da borda inferior e 8cm da borda superior, sendo que cada retângulo de assinatura deve ter 7cm de largura por 3cm de altura, conforme ilustrado na imagem abaixo, escreveríamos o seguinte código:

```java
PadesSigner signer = new PadesSigner();
...
PadesVisualRepresentation vr = new PadesVisualRepresentation();
PadesVisualAutoPositioning position = new PadesVisualAutoPositioning();
position.setPageNumber(-1); // Define inserção na última página do documento
position.setRowSpacing(1.0); // 1.00cm de espaçamento entre linhas
PadesSize size = new PadesSize(7.0, 3.0); // Largura = 7cm e Altura = 3cm
position.setSignatureRectangleSize(size);
PadesVisualRectangle positionContainer = new PadesVisualRectangle();
positionContainer.setLeft(3.0); // Distância da margem esquerda = 3.00cm
positionContainer.setRight(3.0); // Distância da margem direita = 3.00cm
positionContainer.setBottom(2.0); // Distância da margem inferior = 2.00cm
positionContainer.setTop(8.0); // Distância da margem superior = 8.00cm
position.setContainer(positionContainer);
vr.setPosition(position);
vr.setText(...);
vr.setImage(...);
signer.setVisualRepresentation(vr);
```
> **Observação**
>
> O container que definimos acima tem altura e largura variáveis. Essa é uma das muitas possibilidades de especificação do container de assinaturas. Para mais informações, veja o artigo [Definindo um container](https://docs.lacunasoftware.com/articles/pki-express/java/visual-rep/containers).
As assinaturas realizadas com o código acima terão o resultado esperado conforme a imagem abaixo:

## Posicionamento de texto e imagem
* Texto
Interno ao retângulo da representação visual da assinatura, o PKI Express permite a customização do alinhamento, posição e conteúdo do texto a ser inserido. O posicionamento do texto pode ser controlado através da definição de um `container`. Se nenhum container for definido, o texto preencherá todo o retângulo de assinatura.
O alinhamento do texto dentro do container é customizável pelo parâmetro `horizontalAlign` (`left` ou `right`). O parâmetro `fontSize` permite customizar o tamanho da fonte utilizada no texto da representação.
Para inserir a data e hora da assinatura é necessario colocar o parâmetro `includeSigningTime` como verdadeiro. E a formatação da data e hora da assinatura é customizável pelo parâmetro `signingTimeFormat`.
O parâmetro `text` suporta o uso de *tags* que o PKI Express substitui pelas informações contidas no certificado.
* *Tags* genéricas:
Tag | Valor
----------------- | -----------
`{{name}}` | Valor mais apropriado para ser utilizado como nome do assinante (mais recomendado para esse propósito do que o `{{subject_cn}}`)
`{{national_id}}` | Valor mais apropriado para ser utilizado como número de identidade nacional do assinante. Para certificados ICP-Brasil, corresponde ao CPF do assinante. Para certificados italianos, corresponde ao *codice fiscale* do assinante.
`{{email}}` | Endereço de email do assinante
`{{subject_cn}}` | O campo **Common Name** (CN) do titular (*subject name*) do certificado do signatário
`{{issuer_cn}}` | O campo **Common Name** (CN) do emissor (*issuer name*) do certificado do signatário
* *Tags* específicas para certificados ICP-Brasil:
Tag | Valor
----------------------- | -----------
`{{br_cpf}}` | CPF do titular (para certificados de PJ, CPF do responsável pelo certificado)
`{{br_cpf_formatted}}` | Mesmo que `{{br_cpf}}` porém formatado como **000.000.000-00**
`{{br_cnpj}}` | CNPJ da empresa
`{{br_cnpj_formatted}}` | Mesmo que `{{br_cnpj}}` porém formatado como **00.000.000/0000-00**
`{{br_responsavel}}` | Nome do titular (para certificados de PJ, nome do responsável pelo certificado)
`{{br_company}}` | Nome da empresa
`{{br_oab_numero}}` | Número de Inscrição junto a Seccional da OAB (sem zeros à esquerda)
`{{br_oab_uf}}` | Sigla do Estado da Seccional da OAB
`{{br_rg_numero}}` | Número do RG do titular/responsável (sem zeros à esquerda)
`{{br_rg_emissor}}` | Órgão emissor do RG do titular/responsável
`{{br_rg_uf}}` | UF do órgão emissor do RG do titular/responsável
* *Tags* suportadas para manter a retrocompatibilidade:
Tag | Tag equivalente
---------------------- | ---------------
`{{signerName}}` | `{{name}}`
`{{signerEmail}}` | `{{email}}`
`{{signerNationalId}}` | `{{national_id}}`
`{{issuerCommonName}}` | `{{issuer_cn}}`
`{{br_oab_numbero}}` | `{{br_oab_numero}}`
Como exemplo, definiremos um posicionamento do texto relativo ao retângulo de assinatura conforme a figura abaixo.

```java
// Texto da representação visual
PadesVisualText text = new PadesVisualText("Signed by {{name}}"); // Inclui o nome do signatário
text.setFontSize(10.0); // Define tamanho da fonte do texto
text.setIncludeSigningTime(true); // Inclui a data da assinatura
text.setSigningTimeFormat("dd/MM/yy H:mm:ss zzz"); // Define a formatação da data
// Define container do texto
PadesVisualRectangle container = new PadesVisualRectangle();
container.setLeft(0.0);
container.setTop(0.0);
container.setRight(1.5);
container.setBottom(0.5);
text.setContainer(container);
```
* Imagem
Também é possível definir uma imagem de fundo para o retângulo de assinatura e customizar sua opacidade e alinhamento.
Como exemplo, definiremos um posicionamento do texto relativo ao retângulo de assinatura conforme a figura abaixo.
```java
PadesVisualImage image = new PadesVisualImage();
image.setContent(...);
image.setHorizontalAlign(PadesHorizontalAlign.Right); // Alinha a imagem horizontamente na direita
image.setVerticalAlign(PadesVerticalAlign.Center); // Alinha a imagem verticalmente no centro
```
* É possível remover a representação visual:
```java
signatureStarter.setSuppressDefaultVisualRepresentation(true);
```
## Veja também
* [Exemplos completos de representação visual](https://docs.lacunasoftware.com/articles/pki-express/java/visual-rep/samples)
# [Usando o PKI Express em Python](https://docs.lacunasoftware.com/articles/pki-express/python)
Este artigo mostra como usar o [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) em Python
## Projeto de exemplos
O projeto de exemplos demonstra o uso de PKI Express junto com o componente [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto que utilizando **Python**. Ele encontra-se hospedado no GitHub:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/python/flask
### Executando o projeto
1. [Instale o PKI Express](https://docs.lacunasoftware.com/articles/pki-express/setup)
1. [Baixe o projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou faça um *clone* do [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Em um terminal, navegue ate a pasta `python/flask` e execute o comando `pip install -r requirements.txt` para baixar as dependências do projeto.
1. Defina a variável de ambiente `FLASK_APP` com o nome da aplicação que deve ser executada: `FLASK_APP=sample`
1. Execute a aplicação web: `flask run`
1. Acesse a URL: [http://localhost:5000](http://localhost:5000)
Opcionalmente, você pode criar e ativar seu "virtualenv" para evitar misturar diferentes versões de uma biblioteca:
```sh
virtualenv
source bin/activate (on Windows: .//Scripts/activate)
```
### Pacote do PyPi
O uso do PKI Express em Python deve ser feito com o pacote de PyPi [pkiexpress](https://pypi.org/project/pkiexpress)
Para adicioná-lo ao seu projeto, adicione a linha abaixo ao seu arquivo `requirements.txt`:
```text
pkiexpress==1.10.4
```
Feito isso, execute o comando `pip install -r requirements.txt` para baixar o pacote e suas dependências.
O pacote é de código aberto, hospedado no [GitHub](https://github.com/LacunaSoftware/PkiExpressPython). Você pode fazer um *fork* para fazer
customizações, e até mesmo submeter um *pull request*.
# [Como converter do REST PKI para o PKI Express em Python](https://docs.lacunasoftware.com/articles/pki-express/python/convert-from-restpki)
Este artigo explica as alterações necessárias para converter uma aplicação Python utilizando [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki) para uma aplicação Python que utilize [PKI Express](https://docs.lacunasoftware.com/articles/pki-express).
## Equivalência de políticas
Ao migrar sua aplicação Python do REST PKI para o PKI Express, é importante se atentar aos nomes das políticas usadas na aplicação.
Na tabela a seguir, é possível encontrar as equivalências entre os nomes das políticas em ambas bibliotecas. Na primeira coluna temos os nomes das políticas na biblioteca REST PKI, e na segunda coluna temos os nomes das mesmas políticas na biblioteca PKI Express.
| REST PKI | PKI Express |
|-----------------------------------------|---------------------------------------------------|
| PADES_BASIC | PADES_BASIC_WITH_LTV |
| PADES_BASIC_WITH_PKI_BRAZIL_CERTS | PADES_BASIC_WITH_LTV |
| PADES_T_WITH_PKI_BRAZIL_CERTS | PADES_T |
| PKI_BRAZIL_PADES_ADR_BASICA | PKI_BRAZIL_PADES_ADR_BASICA_WITH_LTV |
| PKI_BRAZIL_PADES_ADR_TEMPO | PKI_BRAZIL_PADES_ADR_TEMPO |
| CADES_BES | CADES_BES_WITH_REVOCATION_VALUES |
| CADES_BES_WITH_SIGNING_TIME_AND_NO_CRLS | CADES_BES |
| PKI_BRAZIL_CADES_ADR_BASICA | PKI_BRAZIL_CADES_ADR_BASICA_WITH_REVOCATION_VALUE |
| PKI_BRAZIL_CADES_ADR_TEMPO | PKI_BRAZIL_CADES_ADR_TEMPO |
| PKI_BRAZIL_CADES_ADR_COMPLETA | PKI_BRAZIL_CADES_ADR_COMPLETA |
| XADES_BES | XADES_BES |
| XML_DSIG_BASIC | XML_DSIG_BASIC |
| PKI_BRAZIL_XADES_ADR_BASICA | PKI_BRAZIL_XML_ADR_BASICA |
| PKI_BRAZIL_XADES_ADR_TEMPO | PKI_BRAZIL_XML_ADR_TEMPO |
| NFE_PADRAO_NACIONAL | NFE_PADRAO_NACIONAL |
# [Confiando em certificados que não sejam da ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-express/python/trusting-in-custom-certificates)
Por padrão, o PKI Express só permite operações realizadas com certificados brasileiros válidos, emitidos no âmbito da ICP-Brasil. Para usar outro tipo de certificado, é preciso configurar a classe [PkiExpressOperator](https://github.com/LacunaSoftware/PkiExpressPython/blob/develop/pkiexpress/pki_express_operator.py) ou **qualquer classe que herde dela**.
## Usando os certificados de teste da Lacuna
Se você estiver testando sua aplicação com nossos certificados fictícios (veja [certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs)), defina o campo `trust_lacuna_test_root` como `True` para confiar nesses certificados:
```python
operator.trust_lacuna_test_root = True
```
## Usando um certificado personalizado
Se você estiver usando outros tipos de certificado, informe à classe do operador o certificado da AC raiz de cada um deles.
```python
operator.add_trusted_root('/path/to/root1.cer')
operator.add_trusted_root('/path/to/root2.cer')
operator.add_trusted_root('/path/to/root3.cer')
```
# [Representação Visual](https://docs.lacunasoftware.com/articles/pki-express/python/visual-rep)
A representação visual na assinatura de PDF é a inclusão de um texto ou imagem contendo informações sobre a assinatura que está interna ao documento. Dependendo do software de visualização do PDF, esta representação pode fazer uma ligação com os dados e propriedades da assinatura e do signatário.
**Cada representação visual é sempre relativa a apenas uma assinatura no PDF e cada assinatura pode ter até uma representação visual.**
Nas seções abaixo exploraremos as opções, parâmetros e customizações na definição de uma representação visual para assinaturas em PDF.
## Elementos da Representação Visual
O array de representação visual possui 3 parâmetros que podem ser definidos: `position`, `text` e `image`. A lista abaixo introduz os elementos da representação visual.
* Posição
A posição do retângulo da representação visual da assinatura pode ser definida de forma automática ou manual.
* Posicionamento Automático
O posicionamento automático é indicado para casos em que pode-se definir uma região (container) em uma página do PDF que irá receber a representação das assinaturas. Este modo irá organizar as assinaturas em série dentro da região especificada. A figura abaixo exemplifica o comportamento do posicionamento automático. O retângulo vermelho representa o container definido no qual as representações visuais serão posicionadas automaticamente em série.

* Posicionamento Manual
O posicionamento manual é indicado para os casos em que cada representação das assinaturas serão inseridas em posições arbitrárias, por exemplo a posição e a página serem informados pelo usuário.

* Texto
As informações textuais a serem inseridas na representação visual da assinatura são customizáveis pelo parâmetro `text`.
* Imagem
É possível incluir uma imagem de fundo na representação visual da assinatura, podendo também customizar sua opacidade e seu alinhamento dentro do retângulo da representação visual. Estas opções são definidas pelo parâmetro `image`.
## Posicionamento da representação da assinatura
No posicionamento, define-se a página na qual a representação da assinatura será inserida, sua posição na página e a unidade de medida utilizada para informar a posição.
A página é especificada pelo parâmetro `pageNumber`.
Números negativos são contados a partir do final do documento (-1 significa última página, -2 penúltima, etc).
Pode-se usar o valor especial 0 (zero), que especifica que a representação da assinatura deve ser colocada em uma nova página em branco adicionada ao final do documento.
A unidade de medida é especificada pelo parâmetro `measurementUnits`. Por padrão o seu valor é `"centimeters"`, mas também pode ser `"pdfPoints"`.
### Posicionamento Manual
A posição do retângulo de assinatura em relação à página é especificada pelo parâmetro `manual`. Um retângulo pode ter os parâmetros `top`, `bottom`, `left`, `right`, `width` e `height` conforme mostrado na figura abaixo.

Por exemplo, para especificar que a representação visual deve ser inserida na última página do documento, tendo tamanho
7cm de largura e 3cm de altura, ficando a 2.5cm das margens esquerda e inferior, conforme a imagem abaixo, escreveríamos
o seguinte código:

```python
signer = PadesSigner()
...
visual = {
'position': {
'pageNumber': -1, # Define inserção na última página do documento
'manual': {
'width': 7, # Largura = 7cm
'height': 3, # Altura = 3cm
'left': 2.50, # Distância da margem esquerda = 2.50cm
'bottom': 2.50 # Distância da margem inferior = 2.50cm
}
},
'text': {...},
'image': {...}
}
signer.set_visual_representation(visual)
```
Para mais informações sobre como definir a posição da representação da assinatura, veja o artigo
[Definindo um container](https://docs.lacunasoftware.com/articles/pki-express/python/visual-rep/containers).
### Posicionamento Automático
No posicionamento automático, ao invés de se definir o posicionamento da representação visual em relação à página, define-se um
container que irá receber as representações das sucessivas assinaturas realizadas no documento e o tamanho de cada
retângulo de assinatura dentro do container.
O container de assinaturas é definido pelo parâmetro `container`.
De maneira similar à especificação do retângulo de assinatura no posicionamento manual, o container pode ter os parâmetros
`top`, `bottom`, `left`, `right`, `width` e `height`.
Já o tamanho de cada retângulo de assintura (`width` e `height`) é definido pelo parâmetro `signatureRectangleSize`.
A propriedade `rowSpacing` define o espaçamento entre as "linhas" das representações visuais.
Quando não há mais espaço em uma linha, as representações são inseridas em uma nova linha respeitando o `rowSpacing` passado.
Por padrão, as assinaturas são inseridas da esquerda para direita, de cima para baixo. No entanto, esse comportamento pode ser customizado com os parâmetros `horizontalDirection` (`"rightToLeft"` ou `"leftToRight"`) e `verticalDirection` (`"topDown"` ou `"bottomUp"`).
Por exemplo, para especificar que as representações visuais devem ser inseridas sucessivamente na última página do documento dentro de um container espaçado 3cm das bordas laterais, 2cm da borda inferior e 8cm da borda superior, sendo que cada retângulo de assinatura deve ter 7cm de largura por 3cm de altura, conforme ilustrado na imagem abaixo, escreveríamos o seguinte código:

```python
signer = PadesSigner()
...
visual = {
'position': {
'pageNumber': -1, # Define inserção na última página do documento
'auto': {
'container': {
'left': 3.00, # Distância da margem esquerda = 3.00cm
'right': 3.00, # Distância da margem direita = 3.00cm
'bottom': 2.00, # Distância da margem inferior = 2.00cm
'top': 8.00 # Distância da margem superior = 8.00cm
},
'signatureRectangleSize': {
'height': 3, # Altura = 3cm
'width': 7 # Largura = 7cm
},
'rowSpacing': 1.00 # 1.00cm de espaçamento entre linhas
}
},
'text': {...},
'image': {...}
}
signer.set_visual_representation(visual)
```
> **Observação**
>
> O container que definimos acima tem altura e largura variáveis. Essa é uma das muitas possibilidades de especificação do container de assinaturas. Para mais informações, veja o artigo [Definindo um container](https://docs.lacunasoftware.com/articles/pki-express/python/visual-rep/containers).
As assinaturas realizadas com o código acima terão o resultado esperado conforme a imagem abaixo:

## Posicionamento de texto e imagem
* Texto
Interno ao retângulo da representação visual da assinatura, o PKI Express permite a customização do alinhamento, posição e conteúdo do texto a ser inserido. O posicionamento do texto pode ser controlado através da definição de um `container`. Se nenhum container for definido, o texto preencherá todo o retângulo de assinatura.
O alinhamento do texto dentro do container é customizável pelo parâmetro `horizontalAlign` (`left` ou `right`). O parâmetro `fontSize` permite customizar o tamanho da fonte utilizada no texto da representação.
Para inserir a data e hora da assinatura é necessario colocar o parâmetro `includeSigningTime` como verdadeiro. E a formatação da data e hora da assinatura é customizável pelo parâmetro `signingTimeFormat`.
O parâmetro `text` suporta o uso de *tags* que o PKI Express substitui pelas informações contidas no certificado.
* *Tags* genéricas:
Tag | Valor
----------------- | -----------
`{{name}}` | Valor mais apropriado para ser utilizado como nome do assinante (mais recomendado para esse propósito do que o `{{subject_cn}}`)
`{{national_id}}` | Valor mais apropriado para ser utilizado como número de identidade nacional do assinante. Para certificados ICP-Brasil, corresponde ao CPF do assinante. Para certificados italianos, corresponde ao *codice fiscale* do assinante.
`{{email}}` | Endereço de email do assinante
`{{subject_cn}}` | O campo **Common Name** (CN) do titular (*subject name*) do certificado do signatário
`{{issuer_cn}}` | O campo **Common Name** (CN) do emissor (*issuer name*) do certificado do signatário
* *Tags* específicas para certificados ICP-Brasil:
Tag | Valor
----------------------- | -----------
`{{br_cpf}}` | CPF do titular (para certificados de PJ, CPF do responsável pelo certificado)
`{{br_cpf_formatted}}` | Mesmo que `{{br_cpf}}` porém formatado como **000.000.000-00**
`{{br_cnpj}}` | CNPJ da empresa
`{{br_cnpj_formatted}}` | Mesmo que `{{br_cnpj}}` porém formatado como **00.000.000/0000-00**
`{{br_responsavel}}` | Nome do titular (para certificados de PJ, nome do responsável pelo certificado)
`{{br_company}}` | Nome da empresa
`{{br_oab_numero}}` | Número de Inscrição junto a Seccional da OAB (sem zeros à esquerda)
`{{br_oab_uf}}` | Sigla do Estado da Seccional da OAB
`{{br_rg_numero}}` | Número do RG do titular/responsável (sem zeros à esquerda)
`{{br_rg_emissor}}` | Órgão emissor do RG do titular/responsável
`{{br_rg_uf}}` | UF do órgão emissor do RG do titular/responsável
* *Tags* suportadas para manter a retrocompatibilidade:
Tag | Tag equivalente
---------------------- | ---------------
`{{signerName}}` | `{{name}}`
`{{signerEmail}}` | `{{email}}`
`{{signerNationalId}}` | `{{national_id}}`
`{{issuerCommonName}}` | `{{issuer_cn}}`
`{{br_oab_numbero}}` | `{{br_oab_numero}}`
Como exemplo, definiremos um posicionamento do texto relativo ao retângulo de assinatura conforme a figura abaixo.

```python
visual = {
'text': { # Texto da representação visual
'text': 'Signed by {{name}}', # Inclui o nome do signatário
'container': { # Define container do texto
'left': 0,
'top': 0,
'right': 1.5,
'bottom': 0.5
},
'includeSigningTime': true, # Inclui a data da assinatura
'signingTimeFormat': 'dd/MM/yy H:mm:ss zzz', # Define a formatação da data
'fontSize': 10.0 # Define tamanho da fonte do texto
},
'position': {...},
'image': {...}
}
```
* Imagem
Também é possível definir uma imagem de fundo para o retângulo de assinatura e customizar sua opacidade e alinhamento.
```python
signer = PadesSigner()
...
# Cria uma referencia para o arquivo stamp. E posteriormente esse arquivo pode ser
# referenciado como "fref://{alias}" no campo "url" da representação visual
signer.add_file_reference('stamp', get_pdf_stamp_path())
visual = {
'text': {...},
'position': {...},
'image': {
'resource': {
'url': 'fref://stamp', # Utiliza imagem_de_fundo referenciada como 'stamp'
},
'horizontalAlign': 'right', # Alinha a imagem horizontamente na direita
'verticalAlign': 'center', # Alinha a imagem verticalmente no centro
}
}
signer.set_visual_representation(visual)
```
* É possível remover a representação visual:
```python
signer.set_suppress_default_visual_representation(true)
```
## Veja também
* [Exemplos completos de representação visual](https://docs.lacunasoftware.com/articles/pki-express/python/visual-rep/samples)
# [Usando o PKI Express em Node.js](https://docs.lacunasoftware.com/articles/pki-express/nodejs)
Este artigo mostra como usar o [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) em Node.js
## Projeto de exemplos
O projeto de exemplos demonstra o uso de PKI Express junto com o componente [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto que utilizando **NodeJS**. Ele encontra-se hospedado no GitHub:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/nodejs/expressmvc
### Executando o projeto
1. [Instale o PKI Express](https://docs.lacunasoftware.com/articles/pki-express/setup)
1. [Baixe o projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou faça um *clone* do [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Em um terminal, navegue ate a pasta `nodejs/expressmvc` e execute o comando `npm install` para baixar as dependências do projeto.
1. Execute a aplicação web: `npm start`
1. Acesse a URL: [http://localhost:3000](http://localhost:3000)
> Se você utiliza [Yarn](https://yarnpkg.com), você pode executar `yarn install` ou `yarn` para **baixar as dependências** e
> `yarn start` para **executar a aplicação**.
### Pacote do NPM
O uso do PKI Express em Node.js deve ser feito com o pacote do NPM [pki-express](https://www.npmjs.com/package/pki-express)
Para adicioná-lo ao seu projeto, adicione a linha abaixo ao seu arquivo `package.json`:
```json
{
"dependencies": {
"pki-express": "1.16.2"
}
}
```
Feito isso, execute o comando `npm install` para baixar o pacote e suas dependências.
O pacote é de código aberto, hospedado no [GitHub](https://github.com/LacunaSoftware/PkiExpressNode). Você pode fazer um *fork* para fazer
customizações, e até mesmo submeter um *pull request*.
**Demonstrações e exemplos**
- Demonstração: [Timestamp a PDF](https://try.lacunasoftware.com/pt/pki-suite/timestamp-a-pdf)
- Demonstração: [Make a printer-friendly version](https://try.lacunasoftware.com/pt/pki-suite/printer-friendly-version)
- Timestamp a PDF with PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/timestamp-pdf-express.js)
- Sign a file with CAdES and a server key using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/cades-server-key-express.js)
- Sign an XML document using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-signature-express.js)
- Sign an XML document with a server key using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-server-key-express.js)
- Sign a Brazilian NF-e using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-nfe-signature-express.js)
- Make a printer-friendly version of a signed PDF using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/printer-version-pades-express.js)
- Make a printer-friendly version of a CMS/CAdES signature using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/printer-version-cades-express.js)
# [Como converter do REST PKI para o PKI Express em NodeJS](https://docs.lacunasoftware.com/articles/pki-express/nodejs/convert-from-restpki)
Este artigo explica as alterações necessárias para converter uma aplicação NodeJS utilizando [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki) para uma aplicação NodeJS que utilize [PKI Express](https://docs.lacunasoftware.com/articles/pki-express).
## Equivalência de políticas
Ao migrar sua aplicação NodeJS do REST PKI para o PKI Express, é importante se atentar aos nomes das políticas usadas na aplicação.
Na tabela a seguir, é possível encontrar as equivalências entre os nomes das políticas em ambas bibliotecas. Na primeira coluna temos os nomes das políticas na biblioteca REST PKI, e na segunda coluna temos os nomes das mesmas políticas na biblioteca PKI Express.
| REST PKI | PKI Express |
|-----------------------------------------|---------------------------------------------------|
| PADES_BASIC | PADES_BASIC_WITH_LTV |
| PADES_BASIC_WITH_PKI_BRAZIL_CERTS | PADES_BASIC_WITH_LTV |
| PADES_T_WITH_PKI_BRAZIL_CERTS | PADES_T |
| PKI_BRAZIL_PADES_ADR_BASICA | PKI_BRAZIL_PADES_ADR_BASICA_WITH_LTV |
| PKI_BRAZIL_PADES_ADR_TEMPO | PKI_BRAZIL_PADES_ADR_TEMPO |
| CADES_BES | CADES_BES_WITH_REVOCATION_VALUES |
| CADES_BES_WITH_SIGNING_TIME_AND_NO_CRLS | CADES_BES |
| PKI_BRAZIL_CADES_ADR_BASICA | PKI_BRAZIL_CADES_ADR_BASICA_WITH_REVOCATION_VALUE |
| PKI_BRAZIL_CADES_ADR_TEMPO | PKI_BRAZIL_CADES_ADR_TEMPO |
| PKI_BRAZIL_CADES_ADR_COMPLETA | PKI_BRAZIL_CADES_ADR_COMPLETA |
| XADES_BES | XADES_BES |
| XML_DSIG_BASIC | XML_DSIG_BASIC |
| PKI_BRAZIL_XADES_ADR_BASICA | PKI_BRAZIL_XML_ADR_BASICA |
| PKI_BRAZIL_XADES_ADR_TEMPO | PKI_BRAZIL_XML_ADR_TEMPO |
| PKI_BRAZIL_NFE_PADRAO_NACIONAL | NFE_PADRAO_NACIONAL |
# [Confiando em certificados que não sejam da ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-express/nodejs/trusting-in-custom-certificates)
Por padrão, o PKI Express só permite operações realizadas com certificados brasileiros válidos, emitidos no âmbito da ICP-Brasil. Para usar outro tipo de certificado, é preciso configurar a classe [PkiExpressOperator](https://github.com/LacunaSoftware/PkiExpressNode/blob/develop/lib/pkiexpress-operator.js) ou **qualquer classe que herde dela**.
## Usando os certificados de teste da Lacuna
Se você estiver testando sua aplicação com nossos certificados fictícios (veja [certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs)), defina o campo `trustLacunaTestRoot` como `true` para confiar nesses certificados:
```js
operator.trustLacunaTestRoot = true;
```
## Usando um certificado personalizado
Se você estiver usando outros tipos de certificado, informe à classe do operador o certificado da AC raiz de cada um deles.
```js
operator.addTrustedRootSync("/path/to/root1.cer");
operator.addTrustedRootSync("/path/to/root2.cer");
operator.addTrustedRootSync("/path/to/root3.cer");
```
# [Representação Visual](https://docs.lacunasoftware.com/articles/pki-express/nodejs/visual-rep)
A representação visual na assinatura de PDF é a inclusão de um texto ou imagem contendo informações sobre a assinatura que está interna ao documento. Dependendo do software de visualização do PDF, esta representação pode fazer uma ligação com os dados e propriedades da assinatura e do signatário.
**Cada representação visual é sempre relativa a apenas uma assinatura no PDF e cada assinatura pode ter até uma representação visual.**
Nas seções abaixo exploraremos as opções, parâmetros e customizações na definição de uma representação visual para assinaturas em PDF.
## Elementos da Representação Visual
O array de representação visual possui 3 parâmetros que podem ser definidos: `position`, `text` e `image`. A lista abaixo introduz os elementos da representação visual.
* Posição
A posição do retângulo da representação visual da assinatura pode ser definida de forma automática ou manual.
* Posicionamento Automático
O posicionamento automático é indicado para casos em que pode-se definir uma região (container) em uma página do PDF que irá receber a representação das assinaturas. Este modo irá organizar as assinaturas em série dentro da região especificada. A figura abaixo exemplifica o comportamento do posicionamento automático. O retângulo vermelho representa o container definido no qual as representações visuais serão posicionadas automaticamente em série.

* Posicionamento Manual
O posicionamento manual é indicado para os casos em que cada representação das assinaturas serão inseridas em posições arbitrárias, por exemplo a posição e a página serem informados pelo usuário.

* Texto
As informações textuais a serem inseridas na representação visual da assinatura são customizáveis pelo parâmetro `text`.
* Imagem
É possível incluir uma imagem de fundo na representação visual da assinatura, podendo também customizar sua opacidade e seu alinhamento dentro do retângulo da representação visual. Estas opções são definidas pelo parâmetro `image`.
## Posicionamento da representação da assinatura
No posicionamento, define-se a página na qual a representação da assinatura será inserida, sua posição na página e a unidade de medida utilizada para informar a posição.
A página é especificada pelo parâmetro `pageNumber`.
Números negativos são contados a partir do final do documento (-1 significa última página, -2 penúltima, etc).
Pode-se usar o valor especial 0 (zero), que especifica que a representação da assinatura deve ser colocada em uma nova página em branco adicionada ao final do documento.
A unidade de medida é especificada pelo parâmetro `measurementUnits`. Por padrão o seu valor é `"centimeters"`, mas também pode ser `"pdfPoints"`.
### Posicionamento Manual
A posição do retângulo de assinatura em relação à página é especificada pelo parâmetro `manual`. Um retângulo pode ter os parâmetros `top`, `bottom`, `left`, `right`, `width` e `height` conforme mostrado na figura abaixo.

Por exemplo, para especificar que a representação visual deve ser inserida na última página do documento, tendo tamanho
7cm de largura e 3cm de altura, ficando a 2.5cm das margens esquerda e inferior, conforme a imagem abaixo, escreveríamos
o seguinte código:

```javascript
const signer = new PadesSigner();
...
let visual = {
position: {
pageNumber: -1, // Define inserção na última página do documento
manual: {
width: 7, // Largura = 7cm
height: 3, // Altura = 3cm
left: 2.50, // Distância da margem esquerda = 2.50cm
bottom: 2.50 // Distância da margem inferior = 2.50cm
}
},
text: {...},
image: {...}
};
signer.setVisualRepresentation(visual);
```
Para mais informações sobre como definir a posição da representação da assinatura, veja o artigo
[Definindo um container](https://docs.lacunasoftware.com/articles/pki-express/nodejs/visual-rep/containers).
### Posicionamento Automático
No posicionamento automático, ao invés de se definir o posicionamento da representação visual em relação à página, define-se um
container que irá receber as representações das sucessivas assinaturas realizadas no documento e o tamanho de cada
retângulo de assinatura dentro do container.
O container de assinaturas é definido pelo parâmetro `container`.
De maneira similar à especificação do retângulo de assinatura no posicionamento manual, o container pode ter os parâmetros
`top`, `bottom`, `left`, `right`, `width` e `height`.
Já o tamanho de cada retângulo de assintura (`width` e `height`) é definido pelo parâmetro `signatureRectangleSize`.
A propriedade `rowSpacing` define o espaçamento entre as "linhas" das representações visuais.
Quando não há mais espaço em uma linha, as representações são inseridas em uma nova linha respeitando o `rowSpacing` passado.
Por padrão, as assinaturas são inseridas da esquerda para direita, de cima para baixo. No entanto, esse comportamento pode ser customizado com os parâmetros `horizontalDirection` (`"rightToLeft"` ou `"leftToRight"`) e `verticalDirection` (`"topDown"` ou `"bottomUp"`).
Por exemplo, para especificar que as representações visuais devem ser inseridas sucessivamente na última página do documento dentro de um container espaçado 3cm das bordas laterais, 2cm da borda inferior e 8cm da borda superior, sendo que cada retângulo de assinatura deve ter 7cm de largura por 3cm de altura, conforme ilustrado na imagem abaixo, escreveríamos o seguinte código:

```javascript
const signer = new PadesSigner();
...
let visual = {
position: {
pageNumber: -1, // Define inserção na última página do documento
auto: {
container: {
left: 3.00, // Distância da margem esquerda = 3.00cm
right: 3.00, // Distância da margem direita = 3.00cm
bottom: 2.00, // Distância da margem inferior = 2.00cm
top: 8.00 // Distância da margem superior = 8.00cm
},
signatureRectangleSize: {
height: 3, // Altura = 3cm
width: 7 // Largura = 7cm
},
rowSpacing: 1.00 // 1.00cm de espaçamento entre linhas
}
},
text: {...},
image: {...}
};
signer.setVisualRepresentation(visual);
```
> **Observação**
>
> O container que definimos acima tem altura e largura variáveis. Essa é uma das muitas possibilidades de especificação do container de assinaturas. Para mais informações, veja o artigo [Definindo um container](https://docs.lacunasoftware.com/articles/pki-express/nodejs/visual-rep/containers).
As assinaturas realizadas com o código acima terão o resultado esperado conforme a imagem abaixo:

## Posicionamento de texto e imagem
* Texto
Interno ao retângulo da representação visual da assinatura, o PKI Express permite a customização do alinhamento, posição e conteúdo do texto a ser inserido. O posicionamento do texto pode ser controlado através da definição de um `container`. Se nenhum container for definido, o texto preencherá todo o retângulo de assinatura.
O alinhamento do texto dentro do container é customizável pelo parâmetro `horizontalAlign` (`left` ou `right`). O parâmetro `fontSize` permite customizar o tamanho da fonte utilizada no texto da representação.
Para inserir a data e hora da assinatura é necessario colocar o parâmetro `includeSigningTime` como verdadeiro. E a formatação da data e hora da assinatura é customizável pelo parâmetro `signingTimeFormat`.
O parâmetro `text` suporta o uso de *tags* que o PKI Express substitui pelas informações contidas no certificado.
* *Tags* genéricas:
Tag | Valor
----------------- | -----------
`{{name}}` | Valor mais apropriado para ser utilizado como nome do assinante (mais recomendado para esse propósito do que o `{{subject_cn}}`)
`{{national_id}}` | Valor mais apropriado para ser utilizado como número de identidade nacional do assinante. Para certificados ICP-Brasil, corresponde ao CPF do assinante. Para certificados italianos, corresponde ao *codice fiscale* do assinante.
`{{email}}` | Endereço de email do assinante
`{{subject_cn}}` | O campo **Common Name** (CN) do titular (*subject name*) do certificado do signatário
`{{issuer_cn}}` | O campo **Common Name** (CN) do emissor (*issuer name*) do certificado do signatário
* *Tags* específicas para certificados ICP-Brasil:
Tag | Valor
----------------------- | -----------
`{{br_cpf}}` | CPF do titular (para certificados de PJ, CPF do responsável pelo certificado)
`{{br_cpf_formatted}}` | Mesmo que `{{br_cpf}}` porém formatado como **000.000.000-00**
`{{br_cnpj}}` | CNPJ da empresa
`{{br_cnpj_formatted}}` | Mesmo que `{{br_cnpj}}` porém formatado como **00.000.000/0000-00**
`{{br_responsavel}}` | Nome do titular (para certificados de PJ, nome do responsável pelo certificado)
`{{br_company}}` | Nome da empresa
`{{br_oab_numero}}` | Número de Inscrição junto a Seccional da OAB (sem zeros à esquerda)
`{{br_oab_uf}}` | Sigla do Estado da Seccional da OAB
`{{br_rg_numero}}` | Número do RG do titular/responsável (sem zeros à esquerda)
`{{br_rg_emissor}}` | Órgão emissor do RG do titular/responsável
`{{br_rg_uf}}` | UF do órgão emissor do RG do titular/responsável
* *Tags* suportadas para manter a retrocompatibilidade:
Tag | Tag equivalente
---------------------- | ---------------
`{{signerName}}` | `{{name}}`
`{{signerEmail}}` | `{{email}}`
`{{signerNationalId}}` | `{{national_id}}`
`{{issuerCommonName}}` | `{{issuer_cn}}`
`{{br_oab_numbero}}` | `{{br_oab_numero}}`
Como exemplo, definiremos um posicionamento do texto relativo ao retângulo de assinatura conforme a figura abaixo.

```javascript
let visual = {
text: { // Texto da representação visual
text: 'Signed by {{name}}', // Inclui o nome do signatário
container: array ( // Define container do texto
left: 0,
top: 0,
right: 1.5,
bottom: 0.5
},
includeSigningTime: true, // Inclui a data da assinatura
signingTimeFormat: 'dd/MM/yy H:mm:ss zzz', // Define a formatação da data
fontSize: 10.0 // Define tamanho da fonte do texto
},
position: {...},
image: {...}
};
```
* Imagem
Também é possível definir uma imagem de fundo para o retângulo de assinatura e customizar sua opacidade e alinhamento.
Como exemplo, definiremos um posicionamento do texto relativo ao retângulo de assinatura conforme a figura abaixo.
```javascript
const signer = new PadesSigner();
...
// Cria uma referencia para o arquivo stamp. E posteriormente esse arquivo pode ser
// referenciado como "fref://{alias}" no campo "url" da representação visual
signer.addFileReference('stamp', "imagem_de_fundo.png");
visual = {
text: {...},
position: {...},
image: {
resource: {
url: 'fref://stamp', // Utiliza imagem_de_fundo referenciada como 'stamp'
},
horizontalAlign: 'right', // Alinha a imagem horizontamente na direita
verticalAlign: 'center', // Alinha a imagem verticalmente no centro
},
};
signer.setVisualRepresentation(visual);
```
* É possível remover a representação visual:
```javascript
signatureStarter.suppressDefaultVisualRep(true);
```
## Veja também
* [Exemplos completos de representação visual](https://docs.lacunasoftware.com/articles/pki-express/nodejs/visual-rep/samples)
# [Usando o PKI Express em Ruby](https://docs.lacunasoftware.com/articles/pki-express/ruby)
Este artigo mostra como usar o [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) em Ruby
## Projeto de exemplos
O projeto de exemplos demonstra o uso de PKI Express junto com o componente [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto **Ruby** usando o framework [Ruby on Rails](http://rubyonrails.org/). Ele encontra-se hospedado no GitHub:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/ruby/rails
### Executando o projeto
1. [Instale o PKI Express](https://docs.lacunasoftware.com/articles/pki-express/setup)
1. [Baixe o projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou faça um *clone* do [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Em um terminal, navegue ate a pasta `ruby/rails` e execute o comando `bundle install` para baixar as dependências do projeto.
1. Execute a aplicação web: `rails server`
1. Acesse a URL: [http://localhost:3000](http://localhost:3000)
## Biblioteca do cliente
Para usar o PKI Express em aplicações Ruby, use nosso **Ruby Gem** [pki_express](https://rubygems.org/gems/pki_express) declarando em seu `Gemfile`:
```gemspec
gem 'pki_express', '~> 1.3.2'
```
Após isso, faça um `bundle install` para baixar o gem e suas dependências (se você não tiver o Bundler instalado, pegue [aqui](http://bundler.io/))
Como alternativa, você pode instalar o gem diretamente na linha de comando:
```sh
gem install pki_express
```
> **Observação**
>
> As versões 1.3.2 e anteriores do gem chamam `File.exists?`, que o Ruby 3.2 removeu. No Ruby 3.2 ou superior, restaure esse
> método antes da primeira chamada ao PKI Express, por exemplo num initializer, como faz o
> [projeto de exemplos em Rails](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/config/initializers/pki_express_ruby32_compat.rb):
>
> ```ruby
> File.singleton_class.send(:alias_method, :exists?, :exist?) unless File.respond_to?(:exists?)
> ```
O gem é código aberto, hospedado no [GitHub](https://github.com/LacunaSoftware/PkiExpressRuby). Sinta-se à vontade para fazer o *fork* do repositório se precisar de alguma personalização.
# [Como converter do REST PKI para o PKI Express em Ruby](https://docs.lacunasoftware.com/articles/pki-express/ruby/convert-from-restpki)
Este artigo explica as alterações necessárias para converter uma aplicação Ruby utilizando [REST PKI](https://docs.lacunasoftware.com/articles/rest-pki) para uma aplicação Ruby que utilize [PKI Express](https://docs.lacunasoftware.com/articles/pki-express).
## Equivalência de políticas
Ao migrar sua aplicação Ruby do REST PKI para o PKI Express, é importante se atentar aos nomes das políticas usadas na aplicação.
Na tabela a seguir, é possível encontrar as equivalências entre os nomes das políticas em ambas bibliotecas. Na primeira coluna temos os nomes das políticas na biblioteca REST PKI, e na segunda coluna temos os nomes das mesmas políticas na biblioteca PKI Express.
| REST PKI | PKI Express |
|-------------------------------|---------------------------------------------------|
| PADES_BASIC | PADES_BASIC_WITH_LTV |
| PADES_BASIC_WITH_ICPBR_CERTS | PADES_BASIC_WITH_LTV |
| PADES_T_WITH_ICPBR_CERTS | PADES_T |
| PADES_ICPBR_ADR_BASICA | PKI_BRAZIL_PADES_ADR_BASICA_WITH_LTV |
| PADES_ICPBR_ADR_TEMPO | PKI_BRAZIL_PADES_ADR_TEMPO |
| CADES_BES | CADES_BES_WITH_REVOCATION_VALUES |
| CADES_ICPBR_ADR_BASICA | PKI_BRAZIL_CADES_ADR_BASICA_WITH_REVOCATION_VALUE |
| CADES_ICPBR_ADR_TEMPO | PKI_BRAZIL_CADES_ADR_TEMPO |
| CADES_ICPBR_ADR_COMPLETA | PKI_BRAZIL_CADES_ADR_COMPLETA |
| XML_XADES_BES | XADES_BES |
| XML_DSIG_BASIC | XML_DSIG_BASIC |
| XML_ICPBR_ADR_BASICA | PKI_BRAZIL_XML_ADR_BASIC |
| XML_ICPBR_ADR_TEMPO | PKI_BRAZIL_XML_ADR_TEMPO |
| XML_ICPBR_NFE_PADRAO_NACIONAL | NFE_PADRAO_NACIONAL |
# [Projeto de exemplos em Ruby on Rails](https://docs.lacunasoftware.com/articles/pki-express/ruby/rails)
O **projeto de exemplos em Ruby on Rails** mostra como usar o [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) junto com o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)
em um projeto Ruby com o framework [Ruby on Rails](http://rubyonrails.org/). Ele está hospedado no GitHub em:
https://github.com/LacunaSoftware/PkiSuiteSamples/tree/master/ruby/rails
## Executando o projeto
1. [Instale o PKI Express](https://docs.lacunasoftware.com/articles/pki-express/setup)
1. [Baixe o projeto](https://github.com/LacunaSoftware/PkiSuiteSamples/archive/master.zip) ou clone o [repositório](https://github.com/LacunaSoftware/PkiSuiteSamples.git)
1. Em um terminal, navegue até a pasta `ruby/rails` e execute o comando `bundle install` para baixar as dependências
1. Execute a aplicação: `rails server`
1. Acesse a URL [http://localhost:3000](http://localhost:3000)
# [Confiando em certificados que não sejam da ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-express/ruby/trusting-in-custom-certificates)
Por padrão, o PKI Express só permite operações realizadas com certificados brasileiros válidos, emitidos no âmbito da ICP-Brasil. Para usar outro tipo de certificado, é preciso configurar a classe [PkiExpressOperator](https://github.com/LacunaSoftware/PkiExpressRuby/blob/develop/lib/pki_express/pki_express_operator.rb) ou **qualquer classe que herde dela**.
## Usando os certificados de teste da Lacuna
Se você estiver testando sua aplicação com nossos certificados fictícios (veja [certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs)), defina o campo `trust_lacuna_test_root` como `true` para confiar nesses certificados:
```ruby
operator.trust_lacuna_test_root = true
```
## Usando um certificado personalizado
Se você estiver usando outros tipos de certificado, informe à classe do operador o certificado da AC raiz de cada um deles.
```ruby
operator.add_trusted_root('/path/to/root1.cer')
operator.add_trusted_root('/path/to/root2.cer')
operator.add_trusted_root('/path/to/root3.cer')
```
# [Representação Visual](https://docs.lacunasoftware.com/articles/pki-express/ruby/visual-rep)
A representação visual na assinatura de PDF é a inclusão de um texto ou imagem contendo informações sobre a assinatura que está interna ao documento. Dependendo do software de visualização do PDF, esta representação pode fazer uma ligação com os dados e propriedades da assinatura e do signatário.
**Cada representação visual é sempre relativa a apenas uma assinatura no PDF e cada assinatura pode ter até uma representação visual.**
Nas seções abaixo exploraremos as opções, parâmetros e customizações na definição de uma representação visual para assinaturas em PDF.
## Elementos da Representação Visual
O array de representação visual possui 3 parâmetros que podem ser definidos: `position`, `text` e `image`. A lista abaixo introduz os elementos da representação visual.
* Posição
A posição do retângulo da representação visual da assinatura pode ser definida de forma automática ou manual.
* Posicionamento Automático
O posicionamento automático é indicado para casos em que pode-se definir uma região (container) em uma página do PDF que irá receber a representação das assinaturas. Este modo irá organizar as assinaturas em série dentro da região especificada. A figura abaixo exemplifica o comportamento do posicionamento automático. O retângulo vermelho representa o container definido no qual as representações visuais serão posicionadas automaticamente em série.

* Posicionamento Manual
O posicionamento manual é indicado para os casos em que cada representação das assinaturas serão inseridas em posições arbitrárias, por exemplo a posição e a página serem informados pelo usuário.

* Texto
As informações textuais a serem inseridas na representação visual da assinatura são customizáveis pelo parâmetro `text`.
* Imagem
É possível incluir uma imagem de fundo na representação visual da assinatura, podendo também customizar sua opacidade e seu alinhamento dentro do retângulo da representação visual. Estas opções são definidas pelo parâmetro `image`.
## Posicionamento da representação da assinatura
No posicionamento, define-se a página na qual a representação da assinatura será inserida, sua posição na página e a unidade de medida utilizada para informar a posição.
A página é especificada pelo parâmetro `pageNumber`.
Números negativos são contados a partir do final do documento (-1 significa última página, -2 penúltima, etc).
Pode-se usar o valor especial 0 (zero), que especifica que a representação da assinatura deve ser colocada em uma nova página em branco adicionada ao final do documento.
A unidade de medida é especificada pelo parâmetro `measurementUnits`. Por padrão o seu valor é `"centimeters"`, mas também pode ser `"pdfPoints"`.
### Posicionamento Manual
A posição do retângulo de assinatura em relação à página é especificada pelo parâmetro `manual`. Um retângulo pode ter os parâmetros `top`, `bottom`, `left`, `right`, `width` e `height` conforme mostrado na figura abaixo.

Por exemplo, para especificar que a representação visual deve ser inserida na última página do documento, tendo tamanho
7cm de largura e 3cm de altura, ficando a 2.5cm das margens esquerda e inferior, conforme a imagem abaixo, escreveríamos
o seguinte código:

```ruby
signer = PkiExpress::PadesSigner.new
...
vr = PkiExpress::PadesVisualRepresentation.new
position = PkiExpress::PadesVisualManualPositioning.new
# Define inserção na última página do documento
position.page_number = -1
signature_rectangle = PkiExpress::PadesVisualRectangle.new
signature_rectangle.width = 7 # Largura = 7cm
signature_rectangle.height = 3 # Altura = 3cm
signature_rectangle.left = 2.5 # Distância da margem esquerda = 2.50cm
signature_rectangle.bottom = 2.5 # Distância da margem inferior = 2.50cm
position.signature_rectangle(signature_rectangle);
vr.position = position
...
signer.visual_representation = vr
```
Para mais informações sobre como definir a posição da representação da assinatura, veja o artigo
[Definindo um container](https://docs.lacunasoftware.com/articles/pki-express/ruby/visual-rep/containers).
### Posicionamento Automático
No posicionamento automático, ao invés de se definir o posicionamento da representação visual em relação à página, define-se um
container que irá receber as representações das sucessivas assinaturas realizadas no documento e o tamanho de cada
retângulo de assinatura dentro do container.
O container de assinaturas é definido pelo parâmetro `container`.
De maneira similar à especificação do retângulo de assinatura no posicionamento manual, o container pode ter os parâmetros
`top`, `bottom`, `left`, `right`, `width` e `height`.
Já o tamanho de cada retângulo de assintura (`width` e `height`) é definido pelo parâmetro `signatureRectangleSize`.
A propriedade `rowSpacing` define o espaçamento entre as "linhas" das representações visuais.
Quando não há mais espaço em uma linha, as representações são inseridas em uma nova linha respeitando o `rowSpacing` passado.
Por padrão, as assinaturas são inseridas da esquerda para direita, de cima para baixo. No entanto, esse comportamento pode ser customizado com os parâmetros `horizontalDirection` (`"rightToLeft"` ou `"leftToRight"`) e `verticalDirection` (`"topDown"` ou `"bottomUp"`).
Por exemplo, para especificar que as representações visuais devem ser inseridas sucessivamente na última página do documento dentro de um container espaçado 3cm das bordas laterais, 2cm da borda inferior e 8cm da borda superior, sendo que cada retângulo de assinatura deve ter 7cm de largura por 3cm de altura, conforme ilustrado na imagem abaixo, escreveríamos o seguinte código:

```ruby
signer = PkiExpress::PadesSigner.new
...
vr = PkiExpress::PadesVisualRepresentation.new
position = PkiExpress::PadesVisualAutoPositioning.new
# Define inserção na última página do documento
position.page_number = -1
# 1.00cm de espaçamento entre linhas
position.row_spacing = 1.0
size = PkiExpress::PadesSize.new(7.0, 3.0) # Largura = 7cm e Altura = 3cm
position.signature_rectangle_size = size
position_container = PkiExpress::PadesVisualRectangle.new
position_container.left = 3.0 # Distância da margem esquerda = 3.00cm
position_container.right = 3.0 # Distância da margem direita = 3.00cm
position_container.bottom = 2.0 # Distância da margem inferior = 2.00cm
position_container.top = 8.0 # Distância da margem superior = 8.00cm
position.container = position_container
vr.position = position
...
signer.setVisualRepresentation(vr);
```
> **Observação**
>
> O container que definimos acima tem altura e largura variáveis. Essa é uma das muitas possibilidades de especificação do container de assinaturas. Para mais informações, veja o artigo [Definindo um container](https://docs.lacunasoftware.com/articles/pki-express/ruby/visual-rep/containers).
As assinaturas realizadas com o código acima terão o resultado esperado conforme a imagem abaixo:

## Posicionamento de texto e imagem
* Texto
Interno ao retângulo da representação visual da assinatura, o PKI Express permite a customização do alinhamento, posição e conteúdo do texto a ser inserido. O posicionamento do texto pode ser controlado através da definição de um `container`. Se nenhum container for definido, o texto preencherá todo o retângulo de assinatura.
O alinhamento do texto dentro do container é customizável pelo parâmetro `horizontalAlign` (`left` ou `right`). O parâmetro `fontSize` permite customizar o tamanho da fonte utilizada no texto da representação.
Para inserir a data e hora da assinatura é necessario colocar o parâmetro `includeSigningTime` como verdadeiro. E a formatação da data e hora da assinatura é customizável pelo parâmetro `signingTimeFormat`.
O parâmetro `text` suporta o uso de *tags* que o PKI Express substitui pelas informações contidas no certificado.
* *Tags* genéricas:
Tag | Valor
----------------- | -----------
`{{name}}` | Valor mais apropriado para ser utilizado como nome do assinante (mais recomendado para esse propósito do que o `{{subject_cn}}`)
`{{national_id}}` | Valor mais apropriado para ser utilizado como número de identidade nacional do assinante. Para certificados ICP-Brasil, corresponde ao CPF do assinante. Para certificados italianos, corresponde ao *codice fiscale* do assinante.
`{{email}}` | Endereço de email do assinante
`{{subject_cn}}` | O campo **Common Name** (CN) do titular (*subject name*) do certificado do signatário
`{{issuer_cn}}` | O campo **Common Name** (CN) do emissor (*issuer name*) do certificado do signatário
* *Tags* específicas para certificados ICP-Brasil:
Tag | Valor
----------------------- | -----------
`{{br_cpf}}` | CPF do titular (para certificados de PJ, CPF do responsável pelo certificado)
`{{br_cpf_formatted}}` | Mesmo que `{{br_cpf}}` porém formatado como **000.000.000-00**
`{{br_cnpj}}` | CNPJ da empresa
`{{br_cnpj_formatted}}` | Mesmo que `{{br_cnpj}}` porém formatado como **00.000.000/0000-00**
`{{br_responsavel}}` | Nome do titular (para certificados de PJ, nome do responsável pelo certificado)
`{{br_company}}` | Nome da empresa
`{{br_oab_numero}}` | Número de Inscrição junto a Seccional da OAB (sem zeros à esquerda)
`{{br_oab_uf}}` | Sigla do Estado da Seccional da OAB
`{{br_rg_numero}}` | Número do RG do titular/responsável (sem zeros à esquerda)
`{{br_rg_emissor}}` | Órgão emissor do RG do titular/responsável
`{{br_rg_uf}}` | UF do órgão emissor do RG do titular/responsável
* *Tags* suportadas para manter a retrocompatibilidade:
Tag | Tag equivalente
---------------------- | ---------------
`{{signerName}}` | `{{name}}`
`{{signerEmail}}` | `{{email}}`
`{{signerNationalId}}` | `{{national_id}}`
`{{issuerCommonName}}` | `{{issuer_cn}}`
`{{br_oab_numbero}}` | `{{br_oab_numero}}`
Como exemplo, definiremos um posicionamento do texto relativo ao retângulo de assinatura conforme a figura abaixo.

```ruby
# Texto da representação visual
# Inclui o nome do signatário
text = PkiExpress::PadesVisualText.new('Signed by {{name}}')
# Define tamanho da fonte do texto
text.font_size = 10.0
# Inclui a data da assinatura
text.include_signing_time = true
# Define a formatação da data
text.signing_time_format = "dd/MM/yy H:mm:ss zzz"
# Define container do texto
container = PkiExpress::PadesVisualRectangle.new
container.left = 0.0
container.top = 0.0
container.right = 1.5
container.bottom = 0.5
text.container = container
```
* Imagem
Também é possível definir uma imagem de fundo para o retângulo de assinatura e customizar sua opacidade e alinhamento.
Como exemplo, definiremos um posicionamento do texto relativo ao retângulo de assinatura conforme a figura abaixo.
```ruby
image = PkiExpress::PadesVisualImage.new
image.content = pdf_content
# Alinha a imagem horizontamente na direita
image.horizontal_align = PadesHorizontalAlign::RIGHT
# Alinha a imagem verticalmente no centro
image.vertical_align =PadesVerticalAlign::CENTER
```
## Veja também
* [Exemplos completos de representação visual](https://docs.lacunasoftware.com/articles/pki-express/ruby/visual-rep/samples)
# [Autenticação com certificado - PKI Express](https://docs.lacunasoftware.com/articles/pki-express/authentication)
Os usuários entram na sua aplicação com o certificado digital: o [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) emite um nonce (um valor aleatório), o
[Web PKI](https://docs.lacunasoftware.com/articles/web-pki) o assina com o certificado do usuário no navegador, e o PKI Express valida a assinatura e o certificado.
O PKI Express acrescentou isso na 1.4.0, com os comandos `start-auth` e `complete-auth`.
O código é Java, dos [samples do PKI Suite](https://github.com/LacunaSoftware/PkiSuiteSamples), com o `com.lacunasoftware.pkiexpress:pki-express`
(1.20.0); o script do navegador é o mesmo no sample em Node.js.
Para o método em si, veja [Login com certificado digital](https://docs.lacunasoftware.com/articles/pki-guide/cert-auth) no guia de soluções para certificação digital.
## Fluxo
1. **Servidor**: `start()` devolve o nonce e o algoritmo de hash; a página recebe os dois em campos ocultos.
1. **Navegador**: o Web PKI lista os certificados do usuário, lê o que ele escolher e assina o nonce com `signData()`; o script preenche o
certificado e a assinatura e envia o formulário.
1. **Servidor**: `complete()` com o nonce, o certificado e a assinatura devolve os resultados da validação e o certificado.
1. **Servidor**: se os resultados forem válidos, encontre o seu usuário por um campo do certificado e faça o login dele.
## 1. Iniciar a autenticação
```java
Authentication auth = new Authentication();
Util.setPkiDefaults(auth);
AuthStartResult result = auth.start();
Util.setNoCacheHeaders(response);
model.addAttribute("nonce", result.getNonce());
model.addAttribute("digestAlgorithm", result.getDigestAlgorithm());
```
* O nonce vale para uma única tentativa de autenticação. O sample envia headers que impedem o navegador de guardar a página em cache
(`Cache-Control: no-cache, no-store, must-revalidate`, `Pragma: no-cache`, `Expires: -1`), para que o botão voltar não mostre um nonce
antigo.
* `Util.setPkiDefaults()` aplica as raízes confiáveis, que decidem quais certificados são aceitos (veja
[Quais certificados são confiáveis](https://docs.lacunasoftware.com/articles/pki-express/validate-signatures#quais-certificados-são-confiáveis)).
* O PKI Express guarda os nonces que emitiu em um repositório de nonces, a `transferDataFolder` (por padrão, a pasta temporária), para
verificá-los no `complete()`. Para guardar os nonces no seu próprio repositório e verificá-los você mesmo, ative o armazenamento externo.
A biblioteca Python (1.10.4) inverte essa opção: ela só passa o repositório de nonces quando o armazenamento externo está ativo.
TODO(docs): se o PKI Express verifica a reutilização do nonce quando nenhum repositório de nonces é informado.
## 2. Assinar o nonce no navegador
A página coloca `nonce` e `digestAlgorithm` em campos ocultos, com campos para o certificado (`certContent`) e para a assinatura
(`signature`).
Quando o usuário entra, o script lê o certificado escolhido, assina o nonce com `signData()` e envia o formulário:
```js
var selectedCertThumbprint = formElements.certificateSelect.val();
pki.readCertificate(selectedCertThumbprint).success(function (certEncoded) {
pki.signData({
thumbprint: formElements.certificateSelect.val(),
data: formElements.nonceField.val(),
digestAlgorithm: formElements.digestAlgorithmField.val()
}).success(function (signature) {
formElements.signatureField.val(signature);
formElements.certContentField.val(certEncoded);
formElements.form.submit();
});
});
```
* O nonce é o dado a assinar, não um hash, por isso o script usa `signData`, e não `signHash`. Veja
[Qual é a diferença entre as funções signData e signHash?](https://docs.lacunasoftware.com/articles/web-pki/faq/sign-data-vs-hash).
* Antes disso, o script inicializa o Web PKI com `pki.init()` e lista os certificados com `listCertificates()`, marcando os vencidos.
## 3. Concluir a autenticação
```java
Authentication auth = new Authentication();
Util.setPkiDefaults(auth);
auth.setNonce(nonce);
auth.setCertificateBase64(certContent);
auth.setSignature(signature);
AuthCompleteResult result = auth.complete();
ValidationResults vr = result.getValidationResults();
if (!vr.isValid()) {
String vrHtml = vr.toString()
.replaceAll("\n", " ")
.replaceAll("\t", " ");
model.addAttribute("vrHtml", vrHtml);
return "authentication-express/failed";
}
model.addAttribute("userCert", result.getCertificate());
```
* Aplique as mesmas configurações de confiança do início: o sample chama `Util.setPkiDefaults()` nas duas etapas.
* As outras bibliotecas dão outros nomes às entradas: Node.js `setNonce`, `setCertificateFromBase64` (devolve uma Promise) e `setSignature`;
PHP `setNonce`, `setCertificateBase64` e `setSignature`; Python as propriedades `nonce_base64`, `certificate_base64` e
`signature_base64`.
* Um nonce desconhecido falha com o código de retorno 28 (`NonceNotFound`), e uma assinatura inválida com o 08 (`BadSignature`). Veja os
[códigos de retorno](https://docs.lacunasoftware.com/articles/pki-express/return-codes).
## 4. Fazer o login do usuário
Quando os resultados são válidos, procure o seu usuário por um dos campos do certificado, como `getEmailAddress()` ou, em certificados
ICP-Brasil, `getPkiBrazil().getCpf()`; qual campo usar depende da sua aplicação.
A página de sucesso do sample mostra o nome comum do titular, o e-mail, o tipo de certificado ICP-Brasil, CPF, responsável, empresa, CNPJ,
RG e OAB.
## Veja também
* [Validar assinaturas](https://docs.lacunasoftware.com/articles/pki-express/validate-signatures)
* [Login com certificado digital](https://docs.lacunasoftware.com/articles/pki-guide/cert-auth) no guia de soluções para certificação digital
* [Referência rápida do PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference)
**Demonstrações e exemplos**
- Demonstração: [Your certificates in the browser](https://try.lacunasoftware.com/pt/web-pki/certificates-in-the-browser)
- Authenticate with a digital certificate using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/authentication-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/authentication-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/authentication_express.py)
# [Histórico de versões do PKI Express](https://docs.lacunasoftware.com/articles/pki-express/changelog)
### 1.38.0 (2026-08-26)
- [PKIE-12] Adiciona suporte a chaves e certificados em tokens/HSMs PKCS#11 através da configuração `pkcs11LibraryPath`
- [PKIE-86] Adiciona opção metadata nos comandos de assinatura PAdES para definir metadados do PDF
- Atualização Lacuna.Pki SDK [2.22.3](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-22-3)
### 1.36.2 (2026-03-09)
- Atualização Lacuna.Pki SDK [2.22.2](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-22-2)
### 1.36.1 (2026-02-07)
- Atualização Lacuna.Pki SDK [2.22.1](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-22-1)
### 1.36.0 (2025-10-31)
- Adiciona política de assinatura XML para "Certificación de Origen Digital" (COD) usando SHA-256 e Transform extra `xml-exc-c14n#`
### 1.35.1 (2025-08-01)
- Atualização Lacuna.Pki SDK [2.20.1](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-20-1)
### 1.35.0 (2025-06-10)
- [PKIE-85] Adiciona comando de estender carimbos em assinatura de arquivamento XML (e.g.: ICP-Brasil AD-RA)
- [PKIE-85] Melhoria no retorno de informações dos carimbos de tempo no model de assinaturas XML
### 1.34.2 (2025-03-22)
- Atualização do pacote Lacuna.Pki.BrazilTrustServices 1.7.1
### 1.34.1 (2025-03-20)
- Adiciona novo certificado raiz de teste da Lacuna
### 1.34.0 (2025-03-17)
- Atualização PKI SDK [2.18.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-18-0)
- Atualização do target framework
- Atualização de pacotes de dependências
- Adiciona suporte às políticas de assinatura PAdES ICP-Brasil AD-RC e AD-RA
### 1.33.0 (2025-02-20)
- Adiciona funcionalidades de requisição direta de carimbo de tempo para uma dado ou hash: stamp-data e stamp-hash
- Adiciona configuração de auto-retry para o requisitor de carimbos de tempo
- Melhoria na listagem de assinaturas de PDF quando há duplicidade de "signature field name" presente
- Corrige serialização do modelo do objeto SignaturePolicyIdentifier quando há somente policyID
### 1.32.1 (2024-09-26)
- Atualização PKI SDK [2.17.2](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-17-2)
### 1.32.0 (2024-09-06)
- Atualização do pacote PKI Trust Services 1.7.0
- Melhoria no log de erros
- Adiciona política de assinatura XMLDSig básica com algoritmo de hash SHA1 (para suporte a assinaturas de sistema legados APENAS)
### 1.31.2 (2024-04-17)
- Atualização PKI SDK [2.16.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-16-0)
- Melhoria na chamada de ativação
### 1.31.1 (2023-06-29)
- Atualização do pacote BrazilTrustServices com suporte a proxy
### 1.31.0 (2023-01-04)
- Atualização PKI SDK [2.13.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-13-0)
- Adiciona suporte a arquivo P7B como Trust Arbitrator
- Linux versão single-file self-contained
### 1.29.0 (2022-11-16)
- Muda build de linux para .NET 6.0 para suportar novas versões do Ubuntu
### 1.28.1 (2022-09-15)
- Corrigi comportamento quando um provedor de certificado em nuvem retorna mais de um certificado com o mesmo identificador escolhendo um desses certificados
### 1.28.0 (2022-08-25)
- Adiciona suporte a iniciação do processo de autorização no provedor de certificado em nuvem sem a operação de discover (comando `start-service-auth`)
- Adiciona suporte a recuperação do valor `customState` antes de completar a autorização no provedor de cerficado em nuvem (command `get-service-auth-custom-state`)
### 1.27.1 (2022-07-10)
- Atualização PKI SDK [2.12.1](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-12-1)
### 1.27.0 (2022-07-10)
- Adiciona campo de assinatura de PDF: `signer-name` e `location`
- Adiciona opção `classic-enveloped-transform` na assinatura de XML
- Atualização PKI SDK [2.12.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-12-0)
### 1.26.3 (2022-04-25)
- Adiciona opção `trust-uncertified-signing-time` na validação de assinatura de XML
- Adiciona política XAdES ICP-Brasil AD-RB com artefatos de revogação incluídos
- Atualização PKI SDK [2.11.3](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-11-3)
### 1.26.2 (2022-04-22)
- Atualização PKI SDK [2.11.2](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-11-2)
### 1.26.0 (2022-04-19)
- Corrige propagação de _trust arbitrators_ customizados
### 1.26.0 (2022-04-19)
- Adiciona campos de PKIs internacionais ao _certificate model_: Argentina, Ecuador, Paraguay, Peru
- Adiciona campo _certificate policies_ ao _certificate model_
- Adiciona política de asinatura XAdES ICP-Brasil AD-RC (Completa)
- Adiciona política de assinatura CAdES ICP-Brasil AD-RA (Arquivamento)
- Adiciona opção de inserção do elemento de assinatura XML `sig-element-insertion`
- Atualização PKI SDK [2.11.1](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-11-1)
### 1.25.4 (2022-02-02)
- Atualização PKI SDK [2.9.1](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-9-1)
### 1.25.3 (2021-10-26)
- Adiciona suporte a definição da direção do auto posicionamento de representações visuais PAdES
### 1.25.1 (2021-10-22)
- Corrige saída do comando `open-xml` quando o XML tem assinatura do tipo full XML
### 1.25.0 (2021-10-21)
- Adiciona abertura e validação de assinaturas XML (comando `open-xml`)
- Adiciona suporte a politica de arquivamento ICP-Brasil em assinaturas XML (política `adra`)
- Corrige ValidationException quando a flag `json` está habilitada
### 1.24.1 (2021-07-16)
- Corrige bug na configuração do trustServicesDiscoveryTimeout
- Adiciona opção `--guess-certificate` que quando múltiplos certificados são encontrados escolhe o mais recente
### 1.24.0 (2021-07-07)
- Adiciona opção `--session-lifetime` nos comandos `discover-services` e `pwd-auth`, para configurar a duração em segundos da sessão de assinaturas
- Melhora as configurações de cultura
### 1.23.2 (2021-06-04)
- Corrige bug na validação de assinaturas PAdES quando não há algumas das informações requeridas
### 1.23.1 (2021-05-14)
- Corrige bug na abertura de assinaturas PAdES quando não há algumas das informações requeridas
### 1.23.0 (2021-02-22)
- Adiciona suporte para o retorno do conteúdo do certificado nos comandos `open-pades` e `open-cades`, com a opção `--fill-cert-content`
- Adiciona opção `--trust-uncertified-signing-time` nos comandos `open-pades` e `open-cades`, para confiar em signing-time não certificado
### 1.22.0 (2021-01-21)
- Adiciona suporte para o retorno do _commitment type_ de assinaturas CAdES (comando `open-cades`)
- Adiciona suporte para o retorno do conteúdo do certificado no comando `open-cert` com a opção `--fill-content`
- Adiciona suporte para o retorno dos certificados emissores no comando `open-cert` com a opção `--fill-issuer`
### 1.21.0 (2020-11-05)
- Corrige conversão do ValidationItemType
- Melhora o model do certificado para returnar o valor de KeyUsage
- Adiciona suporte para o retorno da versão atual do programa (comando `version`)
### 1.20.0 (2020-10-05)
- Adiciona suporte a validação PAdES segundo diferentes políticas com a opção `--policy`
- Adiciona suporte a configuração do _commitment type_ nos comandos `sign-cades` and `start-cades` com a opção`--commitment-type`
- Adiciona suporte a decodificação e validação de certificados (comando `open-cert`)
### 1.19.0 (2020-09-14)
- Adiciona suporte a geração de chave de proteção de dados (comando `generate-data-protection-key`)
- Adiciona suporte a configuração de chave de proteção de dados
- Atualiza PKI SDK para a versão [2.7.8](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-7-8)
### 1.18.0 (2020-08-20)
- Adiciona suporte a usar hashes do documento em assinaturas CAdES _detached_
- Adiciona suporte a representação visual com `visual-rep` em carimbos de tempo
- Melhora retorno do comando quando finaliza a autenticação com PSCs brasileiras
- Corrigi parse do campo sessionType do comando `discover-services`
- Atualiza PKI SDK para a versão [2.7.5](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-7-5)
### 1.17.0 (2020-07-30)
- Adiciona suporte a assinatura usando certificados em nuvem (integração com PSCs brasileiras)
- Melhora merge de assinaturas CAdEs para aceitar um arquivo de assinatura
### 1.16.0 (2020-06-26)
- Adiciona campo "certificationLevel" na assinatura PAdES
### 1.15.0 (2020-06-17)
- Adiciona campo "customSignatureFieldName" na assinatura PAdES
- Atualiza PKI SDK para a versão [2.7.2](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-7-2)
### 1.14.0 (2020-06-09)
- Adiciona suporte a assinatura (CAdES, PAdES e XAdES) utilizando chave armazenada no Azure Key Vault
- Atualiza PKI SDK para a versão [2.7.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-7-0)
### 1.13.2 (2019-08-21)
- Atualiza PKI SDK para a versão [2.5.3](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-5-3)
### 1.13.1 (2019-07-31)
- Adiciona a opção de não mostrar a representação visual padrão em uma assinatura PAdES com `--suppress-default-visual-rep` habilitado
### 1.13.0 (2019-07-19)
- Adiciona integração com o serviço LAPP
- Adiciona suporte a razão do assinante para uma assinatura PAdES com `--reason` habilitado
- Atualiza PKI SDK para a versão [2.5.1](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-5-1)
### 1.12.3 (2019-05-20)
- Mudanças na compilação
### 1.12.2 (2019-04-01)
- Adiciona suporte a configuração de timeout no download de CRLs e certificados emissores de AC
- Adiciona suporte a configuração de timeout na requisição de um timestamp
- Atualiza PKI SDK para a versão [2.3.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-3-0)
### 1.12.0 (2019-02-05)
- Adiciona suporte as políticas ICP-Brasil em assinaturas PAdES (políticas `adrb`, `adrb-ltv`, e `adrt`)
- Atualiza PKI SDK para a versão [2.2.7](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-2-7)
### 1.11.0 (2018-12-04)
- Adiciona suporte a geração de chave (comando `gen-key`)
- Adiciona suporte a geração de certificado PKCS #12 (comando `create-pfx`)
### 1.10.0 (2018-10-31)
- Adiciona suporte a configuração da cultura e do fuso horário com `--culture` e `--timezone` habilitado nas assinaturas
- Adiciona suporte a configuração do formato de data e hora na representação visual da assinatura PAdES
- Adiciona suporte a listar todos os timezones disponíveis no sistema (comando `list-timezones`)
### 1.9.0 (2018-10-05)
- Adiciona suporte a fusão de assinaturas CMS/CAdES (comando `merge-cms`).
- Melhora validação de assinaturas PAdES e CAdES para suportar políticas com carimbo de tempo.
### 1.8.0 (2018-09-25)
- Adiciona suporte ao retorno do certificado do assinante depois de uma assinatura.
- Atualiza PKI SDK para a versão [2.1.3](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-1-3)
### 1.7.0 (2018-09-20)
- Adiciona suporte a carimbo de tempo (comando `stamp-pdf`) em PDFs.
### 1.6.2 (2018-07-20)
- Corrige bug na ativação manualmente com nova licença
- Atualiza PKI SDK para a versão [2.1.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-1-0)
### 1.6.1 (2018-07-11)
- Corrige bug na ativação que afetava instalação em containers Docker
### 1.6.0 (2018-06-18)
- Adiciona suporte para a política de assinatura _Certificación de Origen Digital_ (COD) nas assinaturas XML.
### 1.5.0 (2018-05-18)
- Adiciona escolha da política de assinatura com `--policy` habilitado nas assinaturas
- Adiciona assinatura com carimbo de tempo (suporte para CAdES, PAdES e XAdES)
- Atualiza PKI SDK para a versão [1.20.16](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v1-20-16), corrigindo bug no preenchimento da cadeia de certificados na assinatura PAdES
### 1.4.1 (2018-05-14)
- Atualiza o PKI SDK para versão [1.20.15](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v1-20-15)
### 1.4.0 (2018-04-10)
- Adiciona autenticação com certificado (comandos `start-auth` e `complete-auth`)
### 1.3.0 (2018-03-02)
- Adiciona abertura e validação de assinaturas PAdES e CAdES (comandos `open-pades` e `open-cades`)
- Adiciona edição de PDF (comando `edit-pdf`), possibilitando elaboração de "versão para impressão"
- Adiciona assinatura de servidor utilizando arquivo PKCS #12 (.pfx)
- Adiciona suporte à [utilização de web proxy](https://docs.lacunasoftware.com/articles/pki-express/config/proxy)
- Atualiza o PKI SDK para versão 2.0.13
### 1.2.1 (2018-01-18)
- Atualiza o PKI SDK para versão 2.0.11, corrigindo bug que causava atrasos devido à falta de conectividade mesmo com `--offline` habilitado
### 1.2.0 (2018-01-17)
- Adiciona flag global `--offline` para realizar assinaturas sem conexão de rede
- Corrige bug que fazia com que o comando `help` não pudesse ser utilizado antes da ativação
- Atualiza o PKI PKI SDK para versão 2.0.10
### 1.1.0 (2017-12-06)
- Adiciona mecanismo de ativação, evitando que as aplicações precisem passar o arquivo de licença em cada chamada
- Melhora o registro de logs
- Adiciona comando `config`
- Atualiza o PKI SDK para versão 2.0.7, corrigindo bug no _cache_ de LCRs em ambientes Linux
### 1.0.3 (2017-11-15)
- Atualiza o PKI SDK para versão 2.0.3, corrigindo bug _PlatformNotSupportedException bug_ em algumas distribuições de Linux
### 1.0.2 (2017-11-07)
- Primeira versão disponível tanto para Linux quanto para Windows
- Atualiza o PKI SDK para versão 2.0.2
- Adiciona informação da versão do programa no comando `help`
### 1.0.1 (2017-10-27)
- Corrige bug na compilação que fazia com que arquivos desnecessários fossem incluidos no pacote
_Versão disponível apenas para Linux_
### 1.0.0 (2017-10-26)
- Primeira versão disponível publicamente
- Comandos disponíveis nessa versão:
- `list-certs`
- `read-cert`
- `import-pfx`
- `sign-data`
- `sign-hash`
- `sign-cades`
- `sign-pades`
- `sign-xml`
- `start-cades`
- `start-pades`
- `start-xml`
- `complete-sig`
- `help`
_Versão disponível apenas para Linux_
# [Códigos de retorno do PKI Express](https://docs.lacunasoftware.com/articles/pki-express/return-codes)
As operações do PKI Express apresentam um conjunto de códigos de retorno que podem ser vistos a seguir, bem como o significado de cada código.
| Códigos de retorno | Significado |
|-------------------------------------|------------------------------------------------------------------------------|
| 00 - Ok | Sucesso na execução da operação |
| 01 - BadSyntax | Parâmetros passados não seguem o formato esperado |
| 02 - UnknownError | Erro desconhecido |
| 03 - FileNotFound | Arquivo não encontrado |
| 04 - BadCertificate | Falha na decodificação do certificado |
| 05 - BadThumbprint | Thumbprint inválido |
| 06 - BadHash | To Sign Hash inválido ou algoritmo de hash não reconhecido |
| 07 - BadData | To Sign Data inválido |
| 08 - BadSignature | Assinatura inválida |
| 09 - BadAlgorithm | Algoritmo inválido |
| 10 - BadPolicy | Política inválida |
| 11 - BadVisualRep | Falha no parser do arquivo de representação visual |
| 12 - UndefinedFileReference | Referência indefinida a arquivo |
| 13 - CertNotFound | Certificado não encontrado |
| 14 - ValidationFailed | Falha na validação |
| 15 - IOError | Erro na leitura de arquivos |
| 16 - NoCertsWithKey | Arquivo PFX não contém certificado com chave privada |
| 17 - NotActivated | PKI Express não ativado |
| 18 - ActivationError | Falha na ativação do PKI Express |
| 19 - BadLicense | Base-64 inválida (da licença de ativação) |
| 20 - LicenseNeeded | Nenhum arquivo de licença fornecido |
| 21 - InvalidLicense | Licença inválida |
| 22 - AccessDenied | Falta permissão para modificar a configuração do PKI Express |
| 23 - CertThumbNeeded | Nenhum thumbprint fornecido |
| 24 - BadPdfChanges | Falha na execução das alterações no PDF |
| 25 - UpdateNeeded | Operação necessita de versão mais recente do PKI Express |
| 26 - DataFileNeeded | Nenhum Data File fornecido |
| 27 - NoEncapsulatedContent | Conteúdo encapsulado faltando |
| 28 - NonceNotFound | Nonce não encontrado |
| 29 - DirectoryNotFound | Diretório não encontrado |
| 30 - TimestampAuthorityNotAvailable | Falha na requisição do carimbo de tempo |
| 31 - PolicyRequiresNetwork | Política escolhida precisa de acesso a internet (modo offline não suportado) |
| 32 - TimestampOptionsRequired | Nenhum endereço URL de autoridade carimbadora fornecido |
| 33 - CultureNotFound | Cultura não encontrada |
| 34 - TimeZoneNotFound | Fuso horário não encontrado |
| 35 - InvalidUtcOffset | Formato inválido (de Offset UTC) |
| 36 - KeySizeNotSupported | Tamanho de chave não suportado |
| 37 - BadKeyFormat | Formato não reconhecido (de chave) |
| 38 - BadKey | Chave inválida |
| 39 - InvalidTimeoutValue | Valor de timeout inválido |
| 40 - CertificateMismatch | Certificado não corresponde a chave armazenada |
| 41 - KeyNotFound | Chave não encontrada |
| 42 - NoCertificateFile | Nenhum certificado fornecido |
| 43 - BadCertificationLevel | Nível de certificação inválido |
## Códigos de retorno nas bibliotecas cliente
As bibliotecas cliente executam o PKI Express a cada operação e transformam qualquer código de retorno diferente de
`00 - Ok` num erro. A forma de ler o código depende da linguagem:
| Linguagem | Uma operação que falha lança | Como ler o código de retorno |
|---|---|---|
| Java | `RuntimeException`, cuja mensagem traz a saída do PKI Express | A exceção não traz o código |
| Node.js | A promise é rejeitada com `ValidationError` para `14 - ValidationFailed` ou, nos demais casos, com um erro chamado `CommandError` | `err.code`; compare-o com os `ErrorCodes` exportados, como `ErrorCodes.VALIDATION_FAILED` |
| PHP | `CommandException`, com a mensagem "The command failed with code *N*: *saída*" | `$e->returnCode` |
| Python | `Exception`, com a mensagem "PKI Express returned non-success code *N*: *saída*" | Apenas na mensagem |
| Ruby | `PkiExpress::CommandError`, cuja mensagem traz a saída do PKI Express | `e.code` é o `Process::Status` do processo do PKI Express: leia o número com `e.code.exitstatus` |
Quando a biblioteca não encontra o PKI Express, ela lança `InstallationNotFoundException` (Java, PHP) ou
`InstallationNotFoundError` (Node.js, Python, Ruby); veja [configurações da biblioteca cliente](https://docs.lacunasoftware.com/articles/pki-express/config#configurações-da-biblioteca-cliente).
Quando uma operação exige uma versão do PKI Express mais nova que a instalada, o código é `01 - BadSyntax`; as bibliotecas
de Java, Node.js, PHP e Python terminam então a mensagem com uma dica que informa a versão necessária (no PHP, a exceção
passa a ser uma `Exception` simples).
Em Node.js e Ruby, `ValidationError` e `InstallationNotFoundError` são subclasses de `CommandError`; assim, em Ruby um
único `rescue` trata todas elas, e nelas o `code` é um número:
```ruby
begin
signer.sign
rescue PkiExpress::CommandError => e
code = e.code.respond_to?(:exitstatus) ? e.code.exitstatus : e.code
# code 14 (ValidationFailed): e.message has the validation results
end
```
## Erros de validação
Erros de validação ocorrem durante assinaturas, autenticações ou inspeções com validação.
| Erros de Validação | Significado |
|-----------------------------------------|--------------------------------------------------------------------------------|
| Success | Validação bem sucedida |
| CertificateNotYetValid | Na data de referência, o certificado ainda não é válido |
| CertificateExpired | Na data de referência, o certificado está expirado |
| CertificateRevoked | Na data de referência, o certificado estava revogado |
| CertificateIssuerNotFound | Emissor do certificado não foi encontrado |
| CertificateRevocationStatusUnknown | Estado de revogação do certificado não pode ser verificado. Provavelmente, o PKI Express não conseguiu acessar os certificados CRLs (veja [LAPP](https://docs.lacunasoftware.com/articles/pki-express/config/lapp)) |
| CertificateChainRootNotTrusted | A raiz da cadeia de certificado não é confiável |
| InvalidCertificateSignature | Assinatura não é válida, baseado na chave pública fornecida para o certificado |
| DecodeError | Não foi possível decodificar parte do objeto sendo validado |
| RequiredSignedAttributeMissing | Assinatura não contém todos os atributos assinados que são requeridos |
| ForbiddenSignedAttributePresent | Assinatura contém um ou mais atributos assinados que são proibidos |
| RequiredUnsignedAttributeMissing | Assinatura não contém todos os atributos não assinados que são requeridos |
| ForbiddenUnsignedAttributePresent | Assinatura contém um ou mais atributos não assinados que são proibidos |
| ContentTypeMismatch | Atributo content-type especifica um tipo de conteúdo diferente do CMS |
| MessageDigestMismatch | Atributo message digest especifica um digest diferente do digest do dado assinado |
| SigningCertificateDigestMismatch | Atributo signing certificate especifica um digest diferente do digest do certificado do assinante |
| SignatureAlgorithmValidationFailed | Validação do algoritmo da assinatura falhou |
| RevocationDataIssuedBeforeGracePeriod | Dados de revogação emitidos antes do fim do período de carência |
| UncertifiedDateReference | A referência de tempo utilizada na validação da assinatura não é certificada |
| SignaturePolicyMismatch | Na validação a política de assinatura difere da política fornecida |
| SigningTimeOutOfCertificateValidity | Assinatura realizada fora do período de validade do certificado |
| UnknownSignedAttributesPresent | Assinatura contém um ou mais atributos assinados não reconhecidos |
| UnknownUnsignedAttributesPresent | Assinatura contém um ou mais atributos não assinados não reconhecidos |
| TimestampWithMoreThanOneSigner | Carimbo de tempo contém mais de um assinante |
| TimestampMessageImprintMismatch | Message-imprint do timestamp não é igual a computada |
| CompleteReferencesMismatch | Referências completas de certificados não são válidas |
| InvalidSignatureTimestamp | Carimbo de tempo da assinatura não é válido |
| InvalidReferencesTimestamp | Carimbo de tempo de referenência não é válido |
| InvalidSigAndRefsTimestamp | Carimbo de tempo da assinatura e de referência são inválidos |
| InvalidArchiveTimestamp | Carimbo de tempo do arquivo não é válido |
| InvalidKeyUsage | Certificado assinante não tem permissão para realizar esse tipo de operação |
| InvalidOcspResponse | Resposta OCSP não é válida |
| UnauthorizedIssuer | Emissor do certificado não é uma AC |
| UnknownRootTrustStatus | Estado de confiança da raiz não conhecido |
| InvalidTsl | TSL não é válido |
| InvalidCrl | Certificado LCR não é válido |
| CertificateIssuerValid | Emissor do certificado é confiável |
| CertificateIssuerInvalid | Emissor do certificado não é confiável |
| CertificateValidationFailed | Validação do certificado falhou |
| SignatureVulnerableToSignerSubstitution | Assinatura é vulnerável a ataques de substituição de signatário |
| InvalidXmlSignatureSchema | Validação do schema da assinatura (XSD)falhou |
| XmlDSigCoreValidationFailed | Validação primária (core validation) da assinatura falhou (o digest ou assinatura estão incorretos) |
| SignatureTimestampIgnored | Um carimbo de tempo de assinatura foi ignorado |
| InvalidCertificationPathLen | Caminho de certificação das ACs é inválido |
| SigningCertificateNotFound | Certificado do signatário não foi encontrado |
| UnauthorizedACIssuer | Emissor do certificado é uma AC, portanto não pode ser usado como emissor de certificados de atributo |
| AlgorithmNotAllowed | Algoritmo não é permitido |
| UnacceptableSignaturePolicy | Política de assinatura não permitida |
| TslNotAvailable | TSL não disponível |
# [Carimbo de tempo em PDF - PKI Express](https://docs.lacunasoftware.com/articles/pki-express/timestamp-pdf)
O [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) pode adicionar um carimbo de tempo a um PDF, o que prova que o arquivo existia, tal como está, naquele momento. Os
samples adicionam um carimbo de tempo de documento com a classe `PadesTimestamper`, usando a autoridade de carimbo de tempo do Rest PKI.
Isso exige o PKI Express 1.7.0 ou posterior, que acrescentou o comando `stamp-pdf`.
O código é Python, dos [samples do PKI Suite](https://github.com/LacunaSoftware/PkiSuiteSamples), com o pacote `pkiexpress` (1.10.4); o
sample em Node.js faz o mesmo.
## Fluxo
1. Crie um `PadesTimestamper` e informe a ele uma autoridade de carimbo de tempo.
1. Defina o PDF e o caminho do arquivo de saída.
1. Chame `stamp()`, que executa o comando `stamp-pdf` do PKI Express e grava o PDF com carimbo de tempo no caminho de saída.
```python
stamper = PadesTimestamper()
set_pki_defaults(stamper)
stamper.set_pdf_from_path(join(current_app.config['APPDATA_FOLDER'],
file_id))
create_app_data() # Guarantees that "app_data" folder exists.
output_file = '%s.pdf' % str(uuid.uuid4())
output_file_path = join(current_app.config['APPDATA_FOLDER'], output_file)
stamper.output_file_path = output_file_path
stamper.stamp()
```
* Em vez de um caminho de saída, você pode definir `overwrite_original_file` para aplicar o carimbo no próprio PDF.
* O PDF também pode ser informado em bytes (`set_pdf_from_raw`) ou em Base64 (`set_pdf_from_base64`).
* `stamp()` não devolve nada: leia o resultado do arquivo de saída.
A classe tem o mesmo nome nas outras bibliotecas cliente; os métodos mudam:
| Biblioteca | PDF de entrada | Arquivo de saída | Carimbo |
|---|---|---|---|
| Node.js | `setPdfFromPath(path)` (devolve uma Promise) | `setOutputFilePath(path)` | `stamp()` (devolve uma Promise) |
| Java | `setPdf(path)` | `setOutputFilePath(path)` | `stamp()` |
| PHP | `setPdfFromPath($path)` | `setOutputFile($path)` | `stamp()` |
| Python | `set_pdf_from_path(path)` | propriedade `output_file_path` | `stamp()` |
Uma representação visual do carimbo de tempo está disponível no PKI Express 1.18.0 e posteriores, por `setVisualRepresentation` nas
bibliotecas Node.js, Java (1.23.1) e PHP.
## A autoridade de carimbo de tempo
Os samples definem a autoridade de carimbo de tempo em uma função auxiliar por onde passam todos os operadores. Eles usam a autoridade de
carimbo de tempo do Rest PKI, autenticada com o token de acesso do Rest PKI, e só a definem quando há um token configurado:
```python
if current_app.config['REST_PKI_ACCESS_TOKEN'] is not None:
authority = TimestampAuthority('https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310')
authority.set_oauth_token_authentication(current_app.config['REST_PKI_ACCESS_TOKEN'])
operator.timestamp_authority = authority
```
* Para obter um token de acesso do Rest PKI, veja [Solicitando carimbos de tempo ao Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/requesting-timestamps).
* Outras autoridades de carimbo de tempo podem autenticar com usuário e senha (`set_basic_authentication`) ou com um certificado cliente SSL,
identificado pelo thumbprint (`set_ssl_authentication`; Java: `setSSLThumbprint`). Veja [Configurando uma autoridade de carimbo de tempo](https://docs.lacunasoftware.com/articles/pki-express/php/configuring-timestamp-authority)
para a versão em PHP.
* A biblioteca Python envia um timeout da requisição de carimbo de tempo (`request_timeout`, padrão 15), e a biblioteca Node.js sempre envia
15; as duas exigem o PKI Express 1.12.2, que acrescentou esse timeout.
TODO(docs): a unidade do timeout da requisição de carimbo de tempo.
* TODO(docs): o que o `stamp-pdf` faz quando não há autoridade de carimbo de tempo definida (os samples só definem uma quando há um token do
Rest PKI configurado).
## Erros
Se a autoridade de carimbo de tempo falhar, o PKI Express devolve o código 30 (`TimestampAuthorityNotAvailable`); o código 32
(`TimestampOptionsRequired`) indica que nenhum endereço de autoridade de carimbo de tempo foi informado. Veja os
[códigos de retorno](https://docs.lacunasoftware.com/articles/pki-express/return-codes).
## Veja também
* [Validar assinaturas](https://docs.lacunasoftware.com/articles/pki-express/validate-signatures), para inspecionar o arquivo com carimbo de tempo
* [Referência rápida do PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference)
* [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core), cujo serviço em nuvem também aplica carimbos de tempo em PDFs
**Demonstrações e exemplos**
- Demonstração: [Timestamp a PDF](https://try.lacunasoftware.com/pt/pki-suite/timestamp-a-pdf)
- Timestamp a PDF with PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/timestamp-pdf-express.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/timestamp_pdf_express.py)
# [Validar assinaturas - PKI Express](https://docs.lacunasoftware.com/articles/pki-express/validate-signatures)
O [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) abre um arquivo assinado no seu servidor, lista os signatários e, quando solicitado, valida cada assinatura:
assinaturas em PDF (PAdES), arquivos CMS/CAdES (`.p7s`) e assinaturas XML.
O PKI Express acrescentou os exploradores de PAdES e CAdES na 1.3.0 e o de XML na 1.25.0.
O código é PHP, dos [samples do PKI Suite](https://github.com/LacunaSoftware/PkiSuiteSamples), a única linguagem com samples para os três
formatos; ele usa o `lacuna/pki-express` (1.17.0).
## Fluxo
1. Crie o explorador do formato: `PadesSignatureExplorer`, `CadesSignatureExplorer` ou `XmlSignatureExplorer`.
1. Aplique as configurações de confiança (veja [Quais certificados são confiáveis](https://docs.lacunasoftware.com/articles/pki-express/validate-signatures#quais-certificados-são-confiáveis)).
1. Defina o arquivo assinado e `validate = true`; sem isso, o explorador só inspeciona as assinaturas.
1. Chame `open()`, que executa o comando `open-pades`, `open-cades` ou `open-xml` do PKI Express e devolve os signatários.
1. Leia o certificado, a data da assinatura e o `validationResults` de cada signatário.
## Assinaturas em PDF (PAdES)
```php
$sigExplorer = new PadesSignatureExplorer();
Util::setPkiDefaults($sigExplorer);
$sigExplorer->setSignatureFile(StorageMock::getDataPath($fileId));
$sigExplorer->validate = true;
$signature = $sigExplorer->open();
```
## Assinaturas CAdES
```php
$sigExplorer = new CadesSignatureExplorer();
Util::setPkiDefaults($sigExplorer);
$sigExplorer->setSignatureFile(StorageMock::getDataPath($fileId));
$sigExplorer->validate = true;
$signature = $sigExplorer->open();
```
Para obter também o conteúdo assinado, defina o caminho em que o PKI Express o grava, com `setExtractContentPath`; os samples em Java,
Node.js e Python fazem isso e verificam `hasEncapsulatedContent` antes de oferecer o arquivo:
```java
CadesSignatureExplorer sigExplorer = new CadesSignatureExplorer();
Util.setPkiDefaults(sigExplorer);
sigExplorer.setSignatureFile(StorageMock.getDataPath(signatureFile));
sigExplorer.setValidate(true);
String outputFile = StorageMock.generateFileId("pdf");
sigExplorer.setExtractContentPath(StorageMock.getDataPath(outputFile));
CadesSignature signature = sigExplorer.open();
```
* Os samples gravam o conteúdo como PDF, qualquer que seja o tipo dele.
* Para uma assinatura destacada (detached), informe os dados assinados com `setDataFileFromPath` (Java: `setDataFile`).
TODO(docs): nenhum sample mostra uma assinatura CAdES destacada.
## Assinaturas XML
`open()` devolve um `XmlSignature` cujos `signers` são as assinaturas do documento. O explorador em PHP exige o PKI Express 1.25.1 ou
posterior; as bibliotecas Node.js e Python não têm explorador de XML, e a biblioteca Java tem um na 1.23.1, mas não na 1.20.0.
```php
$sigExplorer = new XmlSignatureExplorer();
Util::setPkiDefaults($sigExplorer);
$sigExplorer->setSignatureFile(StorageMock::getDataPath($fileId));
$sigExplorer->validate = true;
$signatures = $sigExplorer->open()->signers;
```
Cada signatário XML também tem um `type` e o `signedElement` (`localName`, `namespaceUri`).
## Quais certificados são confiáveis
Por padrão, o PKI Express só confia em certificados emitidos na ICP-Brasil.
A função auxiliar dos samples acrescenta as raízes listadas na configuração deles e, para desenvolvimento, a raiz de teste da Lacuna, que
aceita os [certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs):
```php
$trustedRoots = $config['pkiExpress']['trustedRoots'];
foreach ($trustedRoots as $root) {
$operator->addTrustedRoot(StorageMock::getResourcePath($root));
}
$operator->trustLacunaTestRoot = $config['trustLacunaTestRoot'];
```
* Veja "Confiando em certificados que não sejam da ICP-Brasil" para [Node.js](https://docs.lacunasoftware.com/articles/pki-express/nodejs/trusting-in-custom-certificates), [Java](https://docs.lacunasoftware.com/articles/pki-express/java/trusting-in-custom-certificates),
[PHP](https://docs.lacunasoftware.com/articles/pki-express/php/trusting-in-custom-certificates), [Python](https://docs.lacunasoftware.com/articles/pki-express/python/trusting-in-custom-certificates) e [Ruby](https://docs.lacunasoftware.com/articles/pki-express/ruby/trusting-in-custom-certificates).
* O PKI Express não tem contexto de segurança, ao contrário do Rest PKI: nenhuma das bibliotecas cliente dele tem um.
* A biblioteca Java também pode confiar só na ICP-Brasil (`setTrustOnlyICPBrasil`) ou só nas raízes confiáveis do sistema operacional
(`setTrustOnlySystem`).
## Políticas de validação
No PAdES, o PKI Express 1.20.0 e posteriores aceitam uma política de validação: `pades`, `pades-with-cert-protection`, `pki-brazil`,
`pki-brazil-with-cert-protection` ou `adobe-reader`. Defina-a com `validationPolicy` (PHP) ou `setPolicy` (Java), usando
`StandardSignaturePoliciesForValidation`.
* As bibliotecas Node.js e Python não têm política de validação nos exploradores. A propriedade `signaturePolicy` do Node.js só vale na
assinatura.
* O explorador de XML do Java 1.23.1 recebe um `XmlPoliciesForValidation`.
* Desde o PKI Express 1.23.0, `trustUncertifiedSigningTime` (PHP) ou `setTrustUncertifiedSigningTime` (Java) aceita como referência de data
a data da assinatura declarada pelo signatário.
## Ler os resultados
* Uma assinatura PAdES ou CAdES tem `signers`; cada um tem o seu `certificate`, `signingTime`, `messageDigest`, `signaturePolicy`,
`timestamps` e `validationResults`. Um signatário PAdES também tem `isDocumentTimestamp` e `signatureFieldName`.
* `validationResults` tem `isValid()`, `hasErrors()`, `hasWarnings()` e `getSummary()`, e vira um texto que lista todas as verificações.
* O certificado tem `subjectName`, `emailAddress`, `issuerName`, `serialNumber`, as datas de validade e, em certificados ICP-Brasil,
`pkiBrazil` (`certificateType`, `cpf`, `cnpj`, `responsavel`, `companyName`, `dateOfBirth`, RG e OAB).
A página do sample mostra cada signatário com um selo "valid" ou "invalid" a partir de `isValid()`, a data da assinatura, o resumo da
mensagem (message digest), a política, os campos ICP-Brasil e o texto completo dos resultados da validação.
* O código de retorno 14 (`ValidationFailed`) do PKI Express e os erros de validação estão nos [códigos de retorno](https://docs.lacunasoftware.com/articles/pki-express/return-codes).
* TODO(docs): se o `open-*` com `--validate` falha com o código 14 para uma assinatura inválida ou devolve os resultados com erros (os
samples leem `isValid()` por signatário).
## Veja também
* [Carimbo de tempo em PDF](https://docs.lacunasoftware.com/articles/pki-express/timestamp-pdf)
* [Autenticação com certificado](https://docs.lacunasoftware.com/articles/pki-express/authentication)
* [Referência rápida do PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference)
**Demonstrações e exemplos**
- Demonstração: [Validate a signed file](https://try.lacunasoftware.com/pt/pki-suite/validate-a-signed-file)
- Validate a signed PDF using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenPadesExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/open-pades-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-pades-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/open_pades_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/open_pades_express_controller.rb)
- Validate a CMS/CAdES signature using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/OpenCadesExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/open-cades-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-cades-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/open_cades_express.py)
- Validate a signed XML using PKI Express: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/open-xml-express/index.php)
# [Referência rápida do Signer](https://docs.lacunasoftware.com/articles/signer/quick-reference)
O essencial do Lacuna Signer em uma página, com links para as páginas completas.
## Para que serve
O Lacuna Signer é um gerenciador de documentos e assinaturas: sua aplicação faz upload de um arquivo e define um fluxo de participantes (signatários e aprovadores, em sequência ou em paralelo), e o Signer os notifica, coleta as assinaturas digitais ou eletrônicas deles e devolve o arquivo assinado. Ele funciona como SaaS em [dropsigner.com](https://www.dropsigner.com/) ou como uma instância [on-premises](https://docs.lacunasoftware.com/articles/signer/on-premises) própria, e você integra pela API REST, pelas bibliotecas cliente usadas nos exemplos e por um widget de assinatura que pode ser embutido na sua página.
| Prefira | Quando |
|---|---|
| [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core/quick-reference) | Você quer assinaturas dentro da sua própria aplicação, sem um fluxo de documentos: sua aplicação cria uma sessão, envia o usuário para uma página hospedada pelo Rest PKI Core e recebe o resultado de volta. Ele também valida assinaturas, aplica carimbos de tempo e verifica identidade biométrica. |
| [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/quick-reference) | Sua aplicação é .NET e precisa criar ou validar assinaturas CMS/CAdES, PDF/PAdES ou XmlDSig/XAdES, ou validar certificados X.509, no próprio código. |
| [PKI Express](https://docs.lacunasoftware.com/articles/pki-express/quick-reference) | Você precisa desses mesmos formatos de assinatura em praticamente qualquer linguagem, no seu próprio servidor Windows ou Linux, com um arquivo de licença. |
| [Web PKI](https://docs.lacunasoftware.com/articles/web-pki/quick-reference) | Sua página web precisa acessar os certificados do usuário por JavaScript (listá-los, lê-los, assinar dados) enquanto um SDK no servidor monta a assinatura. O próprio widget do Signer já depende de uma licença do Web PKI para o seu domínio. |
| [Bulk Signer](https://docs.lacunasoftware.com/articles/bulk-signer) | Os arquivos precisam ser assinados em lote na sua própria infraestrutura, com um certificado mantido pelo servidor, sem uma pessoa no processo. O Bulk Signer também pode encaminhar ao Signer os arquivos de um perfil de assinatura quando uma pessoa precisa assinar. |
## Instalação
| Linguagem | Pacote | Versão major atual | Registro |
|---|---|---|---|
| .NET | `Lacuna.Signer.Client` | 1 (1.24.0) | [NuGet](https://www.nuget.org/packages/Lacuna.Signer.Client) |
| Java | `com.lacunasoftware.signer:signer-client` | 2 (2.14.0) | [Maven Central](https://central.sonatype.com/artifact/com.lacunasoftware.signer/signer-client) |
| Node.js | `signer-node-client` | 1 (1.1.0) | [npm](https://www.npmjs.com/package/signer-node-client) |
| PHP | `lacuna/signer-client` | 1 (1.4.10) | [Packagist](https://packagist.org/packages/lacuna/signer-client) |
| JavaScript (navegador), assinatura embutida | `lacuna-signer-widget` | 0 (0.8.0) | [Script na CDN](https://cdn.lacunasoftware.com/libs/signer/lacuna-signer-widget-0.8.0.min.js), [npm](https://www.npmjs.com/package/lacuna-signer-widget) |
Instale com `dotnet add package Lacuna.Signer.Client`, Gradle `implementation 'com.lacunasoftware.signer:signer-client:2.14.0'`, `npm install signer-node-client` ou `composer require lacuna/signer-client`. Veja [Bibliotecas cliente](https://docs.lacunasoftware.com/articles/signer/integration-guide#bibliotecas-cliente).
Os exemplos fixam 1.17.0 (.NET), 2.13.0 (Java), `^1.1.0` (Node.js) e 1.4.10 (PHP), todas na versão major atual. Sem uma biblioteca cliente, chame a API REST diretamente, como fazem os [Primeiros passos](https://docs.lacunasoftware.com/articles/signer/get-started) com curl.
## Configuração e autenticação
* **Chave de API.** Toda requisição leva a chave no header `X-Api-Key`, no formato `nome-da-aplicacao|`. Se você tem permissão para criar aplicações na sua organização, crie uma em **Aplicações** e gere a chave com o botão **Chaves** dela; caso contrário, peça uma chave ao administrador da sua instância. A chave é exibida uma única vez e só enxerga e cria documentos daquela organização. Veja [Primeiros passos](https://docs.lacunasoftware.com/articles/signer/get-started) e o [Guia de Integração](https://docs.lacunasoftware.com/articles/signer/integration-guide).
* **Chaves de administração e de monitoramento.** As APIs de administração (usuários e organizações) exigem uma chave de uma aplicação da organização `Sys Admin`; as APIs de monitoramento de consumo exigem uma com o papel `Monitor`. Veja [APIs de Administração](https://docs.lacunasoftware.com/articles/signer/on-premises/administration) e [Monitoramento de consumo](https://docs.lacunasoftware.com/articles/signer/on-premises/consumption-monitoring).
* **Referência da API.** [Swagger](https://www.dropsigner.com/swagger) e a cópia do OpenAPI em https://docs.lacunasoftware.com/openapi/signer.json, com o título "Dropsigner", versão 2.16.1.
| Ambiente | URL base |
|---|---|
| Produção, em nuvem (Dropsigner SaaS) | `https://www.dropsigner.com` |
| Produção, on-premises | A URL da sua instância, como `https://signer.suaempresa.com.br` |
| Sandbox, para desenvolvimento e testes | `https://signer-lac.azurewebsites.net` |
O cliente recebe a URL da instância e a chave de API; troque as duas juntas ao passar para produção.
```php
use Lacuna\Signer\PhpClient\SignerClient;
$this->endPoint = "https://signer-lac.azurewebsites.net/";
$this->apiKey = "your-application|0000000000000000000000000000000000000000000000000000000000000000";
$this->signerClient = new SignerClient($this->endPoint, $this->apiKey);
```
TODO(docs): as páginas da documentação do Signer não mencionam o sandbox (só os exemplos o mencionam), nem como obter uma organização e uma chave de API nele.
## Operações principais
Os blocos abaixo são em PHP, a linguagem com mais exemplos no catálogo; [Experimente](https://docs.lacunasoftware.com/articles/signer/quick-reference#experimente) traz as mesmas tarefas em C#, Java e Node.js, quando existem.
### Fazer upload de um arquivo
Envie o arquivo primeiro; o `id` de upload da resposta é o que o documento referencia. `POST /api/uploads/bytes` recebe os bytes em Base64, em JSON (o mais simples), e `POST /api/uploads` recebe multipart/form-data (melhor para arquivos grandes); em PHP, `uploadFile`. Veja [Criar documentos](https://docs.lacunasoftware.com/articles/signer/integration/documents).
```php
$filePath = "resources/sample.pdf";
$fileName = basename($filePath);
$file = fopen($filePath, "r");
$uploadModel = new UploadModel($this->signerClient->uploadFile($fileName, $file, "application/pdf"));
fclose($file);
```
### Criar um documento com um fluxo
Crie o documento a partir do upload e defina quem age e em que ordem: cada item de `flowActions` tem um `type` (`Signer`, `Approver`, `SignRule` ou `ApproverRule`), um `step` (ações no mesmo step acontecem em paralelo, e um step maior acontece depois) e um `user` (`name`, `identifier`, `email`). `POST /api/documents`; em PHP, `createDocument`. A resposta traz um par `uploadId` e `documentId` por arquivo. O Signer envia e-mail aos participantes do primeiro passo imediatamente, a menos que você envie `disablePendingActionNotifications: true`. Veja [Criar documentos](https://docs.lacunasoftware.com/articles/signer/integration/documents).
```php
$fileUploadModelBuilder = new FileUploadBuilder($uploadModel);
$fileUploadModelBuilder->setDisplayName("One Signer Sample");
$user = new UsersParticipantUserModel();
$user->setName("Jack Bauer");
$user->setEmail("jack.bauer@mailinator.com");
$user->setIdentifier("75502846369");
$flowActionCreateModel = new FlowActionsFlowActionCreateModel();
$flowActionCreateModel->setType(FlowActionType::SIGNER);
$flowActionCreateModel->setUser($user);
$documentRequest = new DocumentsCreateDocumentRequest();
$documentRequest->setFiles(array($fileUploadModelBuilder->toModel()));
$documentRequest->setFlowActions(array($flowActionCreateModel));
$docResult = $this->signerClient->createDocument($documentRequest)[0];
```
### Obter a URL de assinatura (assinatura embutida)
Para que o participante assine dentro da sua aplicação em vez de pelo e-mail, peça a URL de ação dele, identificando o participante por `identifier` (a documentação também envia `emailAddress`): `POST /api/documents/{id}/action-url`; em PHP, `getActionUrl`. A resposta traz `url`, para redirecionar o usuário à página de assinatura do Signer, e `embedUrl`, que você passa ao método `render` do `LacunaSignerWidget` na sua página. Combine com `disablePendingActionNotifications: true` na criação. O widget exige que o Signer esteja configurado com uma licença do Web PKI para o domínio da sua página. Veja [Assinatura Embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature).
```php
$actionUrlRequest = new DocumentsActionUrlRequest();
$actionUrlRequest->setIdentifier($user->getIdentifier());
$actionUrlResponse = new DocumentsActionUrlResponse($this->signerClient->getActionUrl($docResult->getDocumentId(), $actionUrlRequest));
echo ($actionUrlResponse->getEmbedUrl());
```
### Verificar o status do documento
`GET /api/documents/{id}`; em PHP, `getDocumentDetails`. O documento está finalizado quando `status` é `Concluded` (os demais são `Pending`, `FlowConcluded`, `Refused`, `Canceled` e `Expired`), e um participante concluiu a ação dele quando o item correspondente em `flowActions` tem `status` `Completed`. Em produção, use webhooks em vez de polling. Veja [Acompanhar documentos](https://docs.lacunasoftware.com/articles/signer/integration/tracking#check-document).
```php
$documentDetails = new DocumentsDocumentModel($this->signerClient->getDocumentDetails($document->getDocumentId()));
if ($documentDetails->getIsConcluded()) {
echo "Document concluded";
}
foreach ($documentDetails->getFlowActions() as $item) {
if ($item["status"] == ActionStatus::PENDING) {
$user = new UsersParticipantUserModel($item["user"]);
echo "The status of User " . $user->getName() . " is Pending\n";
}
}
```
### Baixar o arquivo assinado
`GET /api/documents/{id}/content?type=...`; em PHP, `getDocumentContent`. `Signatures` é o arquivo assinado (o PDF com as assinaturas embutidas, ou um `.p7s` para CAdES), `PrinterFriendlyVersion` é um PDF com o manifesto de assinaturas e `Original` é o arquivo enviado. Documentos cancelados, expirados ou recusados só podem ser baixados como `Original` e `PrinterFriendlyVersion`, e um documento que ninguém assinou ainda não tem `Signatures` nem `PrinterFriendlyVersion`: verifique `hasSignature` e `status` antes. `GET /api/documents/{id}/content-b64` devolve os bytes em Base64, em JSON. Veja [Baixar e listar](https://docs.lacunasoftware.com/articles/signer/integration/download-and-list#quando-uma-versão-não-pode-ser-baixada).
```php
$signed = $this->signerClient->getDocumentContent($documentId, DocumentDownloadTypes::SIGNATURES);
file_put_contents("./resources/downloaded-signed.pdf", $signed);
$printerFriendly = $this->signerClient->getDocumentContent($documentId, DocumentDownloadTypes::PRINTER_FRIENDLY_VERSION);
file_put_contents("./resources/downloaded-printer-friendly.pdf", $printerFriendly);
```
### Receber webhooks
Cadastre a sua URL na tela da **Organização**, seção **Integração**, ou com `POST /api/organizations/{orgId}/webhooks` (`url`, `authType`, `token`). O Signer passa então a enviar um `POST` com `{ "type": "...", "data": { ... } }` para `DocumentsCreated`, `DocumentSigned`, `DocumentApproved`, `DocumentRefused`, `DocumentConcluded`, `DocumentCanceled`, `DocumentExpired` e `DocumentDeleted`. Ele não assina o corpo: verifique o header que você escolheu, como `Authorization: Bearer `, responda 2xx imediatamente (qualquer outra resposta gera novas tentativas, por padrão até 10), torne o processamento idempotente e consulte o documento de novo, porque os eventos podem chegar fora de ordem. Veja [Webhooks](https://docs.lacunasoftware.com/articles/signer/webhooks).
```php
if (!hash_equals(self::EXPECTED_AUTHORIZATION, $_SERVER['HTTP_AUTHORIZATION'] ?? '')) {
http_response_code(401);
return;
}
$body = file_get_contents('php://input');
$webhook = json_decode($body, true);
if (($webhook['type'] ?? null) === WebhookTypes::DOCUMENT_CONCLUDED) {
$document = new DocumentsDocumentModel($this->signerClient->getDocumentDetails($webhook['data']['id']));
if ($document->getStatus() !== DocumentStatus::CONCLUDED) {
return;
}
}
```
### Cancelar, recusar ou excluir um documento
`POST /api/documents/{id}/cancellation` e `POST /api/documents/{id}/refusal`, cada um com um `reason`, e `DELETE /api/documents/{id}`; em PHP, `cancelDocument`, `refuseDocument` e `deleteDocument`. Chame a operação de que você precisa. Veja [Ciclo de vida do documento](https://docs.lacunasoftware.com/articles/signer/integration/lifecycle).
```php
$cancelDocument = new DocumentsCancelDocumentRequest();
$cancelDocument->setReason("This is a document cancellation");
$this->signerClient->cancelDocument($documentId, $cancelDocument);
$refuseDocument = new RefusalRefusalRequest();
$refuseDocument->setReason("This is a document refusal");
$this->signerClient->refuseDocument($documentId, $refuseDocument);
$this->signerClient->deleteDocument($documentId);
```
## Erros
A API responde com um código de status HTTP: `400` para um erro de sintaxe, como um campo obrigatório que não foi enviado; `401` quando a chave de API não foi enviada ou é inválida; `403` quando a chave é válida, mas a aplicação não tem permissão para a operação; e `422` para um erro da API, cujo corpo JSON (`ErrorModel`) tem `code`, `message` e `details`. Um id de entidade que não é encontrado também gera `422`. Teste o `code`, e não a mensagem. Veja [Respostas de erro](https://docs.lacunasoftware.com/articles/signer/integration-guide#respostas-de-erro).
Os primeiros que um integrador encontra:
* `401` ou `403` na primeira chamada: a chave é inválida, foi revogada ou pertence a outra organização.
* `DocumentNotFound` e `InvalidFlowAction`, entre os principais códigos que a referência da API lista (a lista não é completa, e novos códigos podem surgir).
* `DocumentHasNoSignatures` (`422`): baixar `Signatures` ou `PrinterFriendlyVersion` de um documento que ninguém assinou ainda.
* `MaxWebhooksLimitReached` (mais webhooks que o limite `MaxWebhooksPerOrganization`, 5 por padrão) e `OperationNotSupportedInPersonalOrganizations` ao cadastrar um webhook.
Em PHP, as chamadas do cliente lançam uma exceção cuja mensagem traz a exceção interna inteira, mas `getDocumentContent` não lança exceção em erros HTTP: ele devolve o corpo JSON do erro, então verifique os detalhes do documento antes de salvar o conteúdo como arquivo.
## Certificados e credenciais de teste
* [Certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#signer): a ICP de testes da Lacuna Software. O cenário de assinatura pública dos exemplos assina no sandbox com um deles, o certificado do CPF 56072386105.
* Sandbox `https://signer-lac.azurewebsites.net`: os exemplos rodam nele com uma chave de exemplo compartilhada; para usar a sua própria organização, troque-a por uma chave sua. Cada execução cria documentos reais e o Signer envia e-mail aos participantes, por isso os exemplos usam endereços `@mailinator.com` fictícios.
* Assinatura em nuvem no widget: o exemplo online usa o certificado de teste do BirdID (CPF 026.102.470-10), pareado por um QR code no aplicativo BirdID. Veja [Assinatura Embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature).
* Por padrão, uma instância do Signer aceita só certificados ICP-Brasil; a página [TrustArbitrator](https://docs.lacunasoftware.com/articles/signer/on-premises/trust-arbitrator) mostra como aceitar outras ACs raiz.
* TODO(docs): quais certificados de teste o Dropsigner e o sandbox aceitam.
## Experimente
Demonstrações online e os exemplos de cada linguagem:
**Demonstrações e exemplos**
- Demonstração: [Sign a document](https://try.lacunasoftware.com/pt/signer/sign-a-document)
- Demonstração: [Sign without leaving your page](https://try.lacunasoftware.com/pt/signer/embedded-signature)
- Sign multiple Signer documents with Web PKI: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchSignerSignatureController.java)
- Sign a document: create it with one signer: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithOneSignerScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithOneSignerScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithOneSignerScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithOneSignerScenario.php)
- Create a document with an attachment: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithAttachmentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithAttachmentScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithAttachmentScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithAttachmentScenario.php)
- Create a document with a description: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithDescriptionScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithDescriptionScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithDescriptionScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithDescriptionScenario.php)
- Merge several PDFs into one document (envelope): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithEnvelopeScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithEnvelopeScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithEnvelopeScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithEnvelopeScenario.php)
- Upload a file as Base64: [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateBase64DocumentScenario.php)
- Upload a file as multipart/form-data: [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/uploadUsingMultipartFormDataScenario.ts)
- Create a document from a large file: [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateLargeDocumentScenario.php)
- Generate a document from a PDF form template and CSV data: [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/GenerateDocumentWithTwoOrMoreSignersScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/GenerateDocumentWithTwoOrMoreSignersScenario.php)
- Create a document with two or more signers, in any order: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithTwoOrMoreSignersWithoutOrderScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithTwoOrMoreSignersWithoutOrderScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithTwoOrMoreSignersWithoutOrderScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithTwoOrMoreSignersWithoutOrderScenario.php)
- Create a document with signers in a fixed order: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithTwoOrMoreSignersWithOrderScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithTwoOrMoreSignersWithOrderScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithTwoOrMoreSignersWithOrderScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithTwoOrMoreSignersWithOrderScenario.php)
- Create a document with an approver: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithApproversScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithApproversScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithApproversScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithApproversScenario.php)
- Create a document with a signing rule (any N of a group): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithSigningRuleScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithSigningRuleScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithSigningRuleScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithSigningRuleScenario.php)
- Pre-position a signature on the document: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithPositionedSignaturesScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithPositionedSignaturesScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithPositionedSignaturesScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithPositionedSignaturesScenario.php)
- Add a signer to an existing document: [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/AddSignerToExistingDocumentScenario.php)
- Change a participant in an ongoing flow: [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/DocumentFlowEditRequestScenario.php)
- Require a CAdES signature on a PDF: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreatePDFDocumentWithCadesSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreatePDFDocumentWithCadesSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createPDFDocumentWithCadesSignatureScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreatePDFDocumentWithCadesSignatureScenario.php)
- Sign a whole XML file (XAdES): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateXMLWithFullDocumentSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateXmlWithFullDocumentSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createXmlWithFullDocumentSignatureScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateXmlWithFullDocumentSignatureScenario.php)
- Sign one element of an XML file (XAdES): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateXMLWithElementSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateXMLWithElementSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createXMLWithElementSignatureScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateXMLWithElementSignatureScenario.php)
- Sign a Brazilian digital diploma (XAdES, Portaria MEC 554/2019): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDigitalDegreeScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDigitalDegreeScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDigitalDegreeScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDigitalDegreeScenario.php)
- Sign with a digital certificate through the API (public signature): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/PublicSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/PublicSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/publicSignatureScenario.ts)
- Sign electronically with an uploaded signature image: [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/ElectronicSignatureWithSignatureImageScenario.java)
- Sign without leaving your page (embedded signature): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/EmbeddedSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/EmbeddedSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/embeddedSignatureScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/EmbeddedSignatureScenario.php)
- Check a document's status and its participants' actions: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CheckDocumentStatusScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CheckDocumentStatusScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CheckDocumentStatusScenario.php)
- Send reminders to participants with pending actions: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/NotifyFlowParticipantsScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/NotifyFlowParticipantsScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/notifyFlowParticipantsScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/NotifyFlowParticipantsScenario.php)
- List documents: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/ListDocumentsScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/ListDocumentsScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/listDocumentsScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/ListDocumentsScenario.php)
- List saved document flows: [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/listDocumentScenario.ts)
- Receive webhooks with a minimal HTTP endpoint: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/WebhookEndpointScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/WebhookEndpointScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/webhookEndpointScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/WebhookEndpointScenario.php)
- Handle document webhooks (signed, approved, refused, concluded): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CheckDocumentStatusScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CheckDocumentStatusScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/checkDocumentStatusScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CheckDocumentStatusScenario.php)
- Handle the invoice-closed billing webhook: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/InvoiceWebhookHandlingScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/InvoiceWebhookHandlingScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/InvoiceWebhookHandlingScenario.php)
- Mark an invoice as paid: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/InvoiceWebhookHandlingScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/InvoiceWebhookHandlingScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/InvoiceWebhookHandlingScenario.php)
- Download a document's versions: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/DownloadDocumentVersionScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/DownloadDocumentVersionScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/downloadDocumentVersionScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/DownloadDocumentVersionScenario.php)
- Cancel a document: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CancelDocumentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CancelDocumentScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/cancelDocumentScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CancelDocumentScenario.php)
- Refuse a document as the organization: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/RefuseDocumentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/RefuseDocumentScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/refuseDocumentScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/RefuseDocumentScenario.php)
- Delete a document: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/DeleteDocumentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/DeleteDocumentScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/deleteDocumentScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/DeleteDocumentScenario.php)
- Upload a new version of a document: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/AddNewDocumentVersionScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/AddNewDocumentVersionScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/addNewDocumentVersionScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/AddNewDocumentVersionScenario.php)
- Create a document in a new folder: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentInNewFolderScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentInNewFolderScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentInNewFolderScenario.php)
- Create a document in an existing folder: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentInExistingFolderScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentInExistingFolder.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentInExistingFolderScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentInExistingFolder.php)
# [Lacuna Signer](https://docs.lacunasoftware.com/articles/signer)
O Lacuna Signer é um gerenciador de assinaturas inteligente. Com ele, você pode coletar diferentes tipos de assinatura com um sistema rápido, intuitivo e customizável.
## Características
* **Assinaturas com validade jurídica**: O Signer realiza assinaturas digitais e eletrônicas de documentos como procurações, propostas comerciais, contratos e laudos médicos
* **Cada cliente com sua própria instância**: As instâncias do Signer, seus bancos de dado e storage, são individuais, isso significa mais controle num ambiente mais personalizado
## *On premises*
Conforme citado acima, o Signer pode ser utilizado em nuvem (modalidade SaaS, em [dropsigner.com](https://www.dropsigner.com/)) ou você pode ter uma instância própria
("on premises"). Veja o artigo [Signer *on premises*](https://docs.lacunasoftware.com/articles/signer/on-premises) para mais informações.
## Veja também
* [Primeiros passos com a API](https://docs.lacunasoftware.com/articles/signer/get-started)
* [Histórico de versões](https://docs.lacunasoftware.com/articles/signer/changelog)
# [Signer *on premises*](https://docs.lacunasoftware.com/articles/signer/on-premises)
O [Signer](https://docs.lacunasoftware.com/articles/signer) pode ser utilizado em nuvem (SaaS) ou você pode ter uma instância na sua própria infraestrutura ("on premises").
## Ambientes suportados
O Signer é compatível com os seguintes ambientes:
* [Windows Server](https://docs.lacunasoftware.com/articles/signer/on-premises/windows)
* [Linux](https://docs.lacunasoftware.com/articles/signer/on-premises/linux)
* [Docker](https://docs.lacunasoftware.com/articles/signer/on-premises/docker)
* [Azure App Services](https://docs.lacunasoftware.com/articles/signer/on-premises/azure)
Clique em um dos ambientes acima para obter instruções específicas de instalação e atualização.
## Veja também
* [Preparando o banco de dados para instalação do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/prepare-database)
# [Signer - Setup em Windows Server](https://docs.lacunasoftware.com/articles/signer/on-premises/windows)
Para instalar uma instância on-premises do Signer em Windows Server, primeiro baixe os binários da versão desejada. A versão mais recente disponível está no link abaixo:
**[Pacote de binários do Signer](https://cdn.lacunasoftware.com/signer/signer-2.22.1.zip)**
## Pré-requisitos
* Windows Server 2012 ou mais recente (qualquer edição)
* SQL Server 2014 ou mais recente (edição recomendada: Standard ou superior)
* Licença do PKI SDK (no formato Base64)
* Licença do Web PKI (formato Base64/binário)
## Requisitos do banco de dados
* Banco de dados com a collation `Latin1_General_100_CI_AI` ou `Latin1_General_CI_AI`.
* Credenciais de um usuário com o role `db_owner`.
Se precisar de ajuda para preparar o banco de dados, [clique aqui](https://docs.lacunasoftware.com/articles/signer/on-premises/prepare-database).
## Instalação
1. Instale o IIS
1. Instale o [NET 8.0 Hosting Bundle](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-aspnetcore-8.0.15-windows-hosting-bundle-installer)
1. Crie a pasta do site do IIS. Exemplo: `C:\inetpub\Signer`
1. Crie a pasta dos logs. Exemplo: `C:\Logs`
1. Crie o site no IIS
1. No pool de aplicativos correspondente:
* Selecione **Configurações Básicas** e defina a **Versão do .NET CLR** como **Sem Código Gerenciado**

* Selecione **Configurações Avançadas** e defina **Carregar Perfil do Usuário** como `True`

1. Extraia os arquivos do pacote de binários para a pasta do site.
> **Aviso**
>
> O .NET Core Hosting Runtime deve ser instalado depois do IIS; caso contrário, ao executar o site, você pode receber o erro `500.19 (0x8007000d)`.
> Se for o seu caso, execute a opção "Reparar" do instalador do Hosting Runtime.
## Configuração
Na pasta de instalação, mova o arquivo **appsettings.iis.json** da pasta **config-templates** para a pasta raiz do site. Em seguida, edite o arquivo
para configurar sua instância.
Este arquivo é organizado em seções cujos nós filhos representam os nomes das configurações. Por exemplo: para configurar o
SupportEmailAddress da seção General, adicione ou edite uma seção com o nome `General` e adicione ou edite o filho com o nome `SupportEmailAddress`:
```json
...
"General": {
"SupportEmailAddress": "support@email.com"
}
...
```
As configurações obrigatórias são apresentadas abaixo.
### Connection string do banco de dados
Na seção **ConnectionStrings**:
* **DefaultConnection**: defina a connection string do banco de dados. Uma connection string típica é assim:
```text
Data Source=SERVER;Initial Catalog=DATABASE;User ID=USERNAME;Password=PASSWORD
```
> **Observação**
>
> Se você criou o banco de dados usando recursos avançados, como log shipping ou mirroring, sua connection string pode ser diferente
### Logging
Na seção **Serilog**, configure o logging da aplicação:
```json
...
"Serilog": {
"MinimumLevel": {
"Default": "Warning"
},
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "C:\\Logs\\Signer.log",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}"
}
}
]
}
...
```
Altere a configuração **path** para o caminho do arquivo de log.
> **Observação**
>
> Lembre-se de conceder acesso total ao usuário da aplicação na pasta de logs
### Configuração do OpenID Connect
A seção **Oidc** configura o servidor OpenID Connect, necessário para o gerenciamento de usuários. É necessária uma *subscription* do [GrantID](https://docs.lacunasoftware.com/articles/grant-id).
Você pode usar uma *subscription* SaaS em [grantid.com](https://grantid.com/) ou executar sua própria instância do GrantID.
* **Authority**: a authority OIDC (ex.: *https://patorum.grantid.com*)
* **ApiEndpoint**: o endpoint da API do servidor OIDC (ex.: *https://api.grantid.com*)
* **ApiName**: o escopo de API que será exigido nos tokens de acesso
* **ClientAppId**: o *client id* do aplicativo do painel
* **AppId**: o *client id* do aplicativo de backend
* **AppSecret**: o *client secret* do aplicativo de backend
* **RequireHttps** (opcional): defina como `false` se o servidor OIDC não usar HTTPS
Veja a página [Configurando uma aplicação SPA](https://docs.lacunasoftware.com/articles/grant-id/on-premises/configure-spa-app) para saber como obter esses valores.
### PKI Suite
Na seção **PkiSuite**:
* **SdkLicense**: sua licença do PKI SDK, no formato Base64 (**obrigatório**)
* **WebLicense**: sua licença do componente Web PKI no formato binário (Base64). Necessária apenas se os usuários forem emitir certificados em seus computadores (procedimento de emissão pela web)
* **WebBrand**: se você tiver uma *setup brand* personalizada no Web PKI, defina-a aqui
### Configurações gerais
Em seguida, na seção **General**:
* **SiteName**: o nome desta instância. Ele é usado nos documentos e e-mails gerados
* **SiteUrl**: URL publicamente acessível do site (ex.: `https://signer.patorum.com/`). Este endereço é usado para compor e-mails com links de volta para o site
* **AutoUpdateDatabase**: por padrão, a aplicação tenta fazer as alterações de modelo no banco de dados após uma atualização (quando necessário). Defina como `false` se a aplicação não tiver permissões de proprietário sobre o banco de dados
* **SupportEmailAddress**: o endereço de e-mail de suporte (usado no rodapé dos e-mails enviados)
### Configuração de bindings
* **UseReverseProxy**: defina como `true` se a instância for executada atrás de um proxy reverso ou balanceador de carga. Caso contrário, pode ser omitida.
### Envio de e-mail
Na seção **Email**:
* **Enabled**: por padrão, o envio de e-mail está habilitado. Para desabilitá-lo, defina esta configuração como `false` e ignore o restante desta seção
* **ServerHost**: hostname do servidor SMTP
* **EnableSsl**: por padrão, a conversação SMTP é feita sobre SSL. Para desabilitar o SSL, defina esta configuração como `false`
* **ServerPort**: por padrão, a conversação SMTP é feita pela porta 587. Defina esta configuração para usar outra porta
* **Username** e **Password**: se o servidor SMTP exigir autenticação, defina estas configurações
* **SenderAddress**: endereço de e-mail a ser usado como remetente (campo *from*)
* **SenderName**: nome a ser usado como nome do remetente (opcional)
## Configurações adicionais
Outras configurações podem ser encontradas na página [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings).
## Iniciando a aplicação
1. Inicie o site no IIS
1. Acesse a URL do site
## Atualizando a aplicação
Antes de atualizar sua instância, recomendamos consultar o [Changelog](https://docs.lacunasoftware.com/articles/signer/changelog) para ver o que mudou da sua
versão atual até a mais recente disponível.
Se alguma das versões incluídas na atualização tiver alterações no modelo do banco de dados ("Atualiza modelo de banco de dados: sim"),
proceda com cuidado, pois a instância tentará atualizar o banco de dados na inicialização.
Nesse cenário, recomendamos escolher uma das seguintes opções:
* Reduzir o número de instâncias para 1 (se você tiver vários servidores).
* Permitir que apenas uma instância atualize o banco de dados. Para isso, adicione ou edite as seguintes configurações em todas as instâncias, exceto uma:
```json
"General": {
"ProcessBackgroundJobs": "false",
"AutoUpdateDatabase": "false"
}
```
### Procedimento de atualização
1. Pare o site no Gerenciador do IIS.
1. Faça um backup do banco de dados e da pasta do site.
1. Baixe o novo pacote de binários e extraia o conteúdo para a pasta do site, sobrescrevendo todos os arquivos.
1. Faça as alterações necessárias no arquivo `appsettings.iis.json`.
1. Inicie o site no Gerenciador do IIS.
> **Observação**
>
> O arquivo `appsettings.iis.json` com as configurações da sua instância será preservado, porque o pacote não contém nenhum arquivo com esse nome.
## Veja também
* [Configurações](https://docs.lacunasoftware.com/articles/signer/on-premises/settings)
* [Controle de acesso](https://docs.lacunasoftware.com/articles/signer/on-premises/access-control)
* [Personalização](https://docs.lacunasoftware.com/articles/signer/on-premises/customization)
# [Setup do Signer em Linux](https://docs.lacunasoftware.com/articles/signer/on-premises/linux)
O [Signer](https://docs.lacunasoftware.com/articles/signer) pode ser instalado nas seguintes distribuições de Linux:
* [Ubuntu Server](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/install-ubuntu)
> **Observação**
>
> Caso necessite suporte a uma distribuição específica, entre em contato conosco.
## Veja também
* [Atualização do Signer em Linux](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/troubleshoot)
# [Resolução de problemas do Signer (Linux)](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/troubleshoot)
Problemas comuns:
> **Observação**
>
> A documentação deste sistema encontra-se em construção. Pedimos desculpas pela inconveniência. Por favor contate-nos caso
> precise de alguma informação que ainda não está documentada.
> **Observação**
>
> Se você está encontrando um problema não listado, tente [verificar os logs de sistema](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/troubleshoot/check-logs) ou entre em contato conosco.
## Veja também
* [Verificando os logs de sistema](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/troubleshoot/check-logs)
# [Verificando os logs de sistema (Linux)](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/troubleshoot/check-logs)
Quanto instalado em Linux, o [Signer](https://docs.lacunasoftware.com/articles/signer) escreve seus logs no *systemd journal*, uma solução para gerenciamento de logs centralizada do Linux. Para
visualizar os logs, utiliza-se o utilitário `journalctl`.
Para verificar os logs do Signer:
```sh
journalctl -u lacuna-signer
```
Utilize a flag `-f` para acompanhar o log de maneira contínua (como o comando `tail -f`):
```sh
journalctl -u lacuna-signer -f
```
Utilize o parâmetro `--since` para visualizar apenas os logs mais recentes:
```sh
journalctl -u lacuna-signer --since "1 min ago"
```
## Veja também
* [*How To Use Journalctl to View and Manipulate Systemd Logs*](https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs) (artigo da DigitalOcean)
* [Resolução de problemas em Linux](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/troubleshoot)
# [Instalação do Signer em Ubuntu Server](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/install-ubuntu)
Para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/signer/on-premises) do [Signer](https://docs.lacunasoftware.com/articles/signer) em Ubuntu Server, siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/signer/on-premises).
## Pré-requisitos
* Ubuntu Server (qualquer versão ainda suportada pelo fabricante, recomendamos a versão LTS mais recente)
* **Licença [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk)** (no formato Base64)
* **Licença [Web PKI](https://docs.lacunasoftware.com/articles/web-pki)** (formato Base64/binário)
* **Entrada de DNS** criadas anteriormente para o site
* **Connection string** para um [banco de dados SQL server previamente criado](https://docs.lacunasoftware.com/articles/signer/on-premises/prepare-database)
* (recomendado) Certificado SSL válido para o site
## Instale o ASP.NET Core Runtime 8.0
> **Info**
>
> Essas instruções assumem que você está autenticado como **root**. Se você não estiver, execute `sudo su -` antes de continuar!
Siga as instruções abaixo dependendo da versão do seu Ubuntu para:
1. Registrar a chave da Microsoft e adicionar o repositório de pacotes (só precisa ser feito em versões anteriores à versão 22.04 e uma única vez por máquina)
1. Instalar o pacote `aspnetcore-runtime-8.0`
### Ubuntu 22.04 or later
```bash
apt-get update
apt-get install aspnetcore-runtime-8.0
```
### Ubuntu 20.04 (LTS)
```bash
curl -O https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb
dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
```
```bash
apt-get update
apt-get install aspnetcore-runtime-8.0
```
### Teste a instalação
Para testar a instalação, execute:
```sh
dotnet --list-runtimes
```
A saída esperada é semelhante a:
```text
Microsoft.AspNetCore.App 8.0.* [*/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 8.0.* [*/dotnet/shared/Microsoft.NETCore.App]
```
> **Dica**
>
> Para outras versões do sistema operacional e métodos alternativos de instalação do ASP.NET Core Runtime, visite [esta página](https://docs.microsoft.com/pt-br/dotnet/core/install/linux)
## Instalar pacotes obrigatórios adicionais
Instale dependências adicionais:
```sh
apt-get install libc6-dev libgdiplus
```
## Instalar o Signer
Crie um usuário local para executar o servidor de aplicação do Signer:
```sh
mkdir /var/lacuna-signer
useradd --system --home-dir /var/lacuna-signer lacuna-signer
chown lacuna-signer:lacuna-signer /var/lacuna-signer
```
Crie a pasta do site, baixe e extraia os binários:
```sh
mkdir /usr/share/lacuna-signer
curl -O https://cdn.lacunasoftware.com/signer/signer-2.22.1.tar.gz
tar xzf signer-2.22.1.tar.gz -C /usr/share/lacuna-signer
chmod -R a=,u+rwX,go+rX /usr/share/lacuna-signer
```
> **Observação**
>
> Os arquivos do site podem ser lidos por qualquer usuário mas só podem ser alterados por usuários com permissões elevadas. Isso significa que o usuário da aplicação (*signer*)
> pode ler os arquivos mas não pode alterá-los (isso é intencional).
Crie o arquivo de configuração do Signer a partir do template fornecido:
```sh
mkdir /etc/lacuna-signer
cp /usr/share/lacuna-signer/config-templates/appsettings.linux.json /etc/lacuna-signer/
chown -R root:lacuna-signer /etc/lacuna-signer
chmod -R a=,u+rwX,g+rX /etc/lacuna-signer
```
> **Observação**
>
> Arquivos de configuração só podem ser lidos por membros do grupo *signer* e só podem ser alterados por usuários com permissões elevadas. Isso é importante para proteger informações
> sigilosas armazenadas no arquivo de configuração dos demais usuários da máquina.
## Configure o Signer
Edite o arquivo de configuração para configurar sua instância do Signer:
```sh
nano /etc/lacuna-signer/appsettings.linux.json
```
### *Connection string* do banco de dados
Na seção **ConnectionStrings**, na configuração **DefaultConnection**, defina a *connection string* para o banco de dados criado anteriormente. Uma *connection string* típica parece como
essa:
```text
Data Source=SERVER;Initial Catalog=DATABASE;User ID=USERNAME;Password=PASSWORD
```
> **Observação**
>
> Se você criar um banco de dados usando características avançadas como *log shipping* ou *mirroring*, sua *connection string* pode ser diferente.
### Configurações gerais
Gere uma chave de 256 bits para cifrar segredos armazenadas em banco de dados:
```sh
openssl rand -base64 32
```
Preencha, então, na seção **General**:
* **SiteUrl**: URL publicamente acessível do site (ex: `https://assinador.patorum.com/`). Este endereço é usado para compor emails com links de volta ao site.
* **SiteName**: nome do site, usado como título das páginas web (ex: *Assinador Patorum*)
* **EncryptionKey**: chave gerada acima
* **SupportEmailAdress**: o endereço de e-mail de suporte (usado no rodapé dos e-mails enviados)
* **Theme** (opcional): esquema de cores do site -- esquemas disponíveis:
* `acr`: amazon + cornell-red
* `alg`: azure-lime + green
* `clg`: cerulean-lime + green
* `cam`: charcoal + amazonite
* `clc`: cobalt-lemon + curry
* `dcg`: dark-cerulean + green
* `dgy`: dark-grey + yellow
* `dir`: dark-indigo + red
* `eva`: english-vermillion + arsenic
* `gdc`: green + dark-coral
* `idg`: independence-green
* `osg`: onyx + satin-gold
* `qbm`: queen-blue + mint
* `tbg`: teal-blue + gold
* **PersonalAccountsEnabled**:
* Atribua o valor `true` para deixar o sistema "aberto", ou seja, permitir que usuários se registrem e utilizem livremente o sistema (sem aprovação de um administrador)
* Atribua o valor `false` para deixar o sistema "fechado", ou seja, exigir que usuários sejam previamente cadastrados em uma organização para poderem utilizar o sistema
* **EnableDocumentTypes**: controla se a seleção de tipo de documento será exibida ao criar documentos
* **EnableElectronicSignature**: controla se assinaturas eletrônicas (sem certificado digital) estarão habilitadas
### Bindings
Na seção **Bindings**
* **HttpsMode**: por padrão, o painel e as REST APIs só podem ser acessados por meio de HTTPS, que é o comportamento recomendado se você tiver um certificado SSL válido.
* Se você não tiver um certificado SSL válido, definir esta configuração como `Optional`. Os usuários que acessam o painel não serão redirecionados para HTTPS, e as REST APIs poderão ser acessadas por meio de HTTP.
* Se você tiver um certificado SSL válido, mas alguns aplicativos clientes herdados não o
reconhecerem, defina essa configuração como `RedirectPages`. As REST APIs ainda estarão acessíveis por meio do HTTP (como no modo `Opcional`), mas os usuários que acessam o painel serão redirecionados para o HTTPS.
* **SslPort**: por padrão, os usuários que acessam o painel por meio de HTTP são redirecionados para HTTPS na porta TCP padrão 43. Se o site estiver usando HTTPS em uma porta não padrão, defina-o aqui.
### PKI Suite
Na seção **PkiSuite**:
* **SdkLicense**: sua licença para PKI SDK, no formato Base64 (**obrigatório**)
* **WebLicense**: sua licença para o componente Web PKI no formato binário (Base64) (**obrigatório**)
### Envio de email
Na seção **Email**:
* **Enabled**: por padrão, o envio de email está habilitado. Para desabilitar, defina esta configuração como `false` e ignore o restante desta seção.
* **ServerHost**: *hostname* do servidor SMTP
* **EnableSsl**: por padrão, a conversação SMTP é executada por SSL. Para desativar o SSL, defina essa configuração como `false`
* **ServerPort**: Por padrão, a conversação SMTP é realizada pela porta 587. Defina esta configuração para usar uma porta diferente
* **Username** ou **Password**: se o servidor SMTP exigir autenticação, defina essas configurações
* **SenderAddress**: endereço de e-mail a ser usado como remetente (do campo)
* **SenderName**: nome a ser usado como o nome do remetente (opcional)
### Integração com provedor de OpenID Connect
O Signer requer um provedor de Open ID Connect (OIDC), mais especificamente uma *subscription* do [GrantID](https://docs.lacunasoftware.com/articles/grant-id).
Você pode usar uma *subscription* em nosso serviço SaaS em [grantid.com](https://grantid.com/) ou [instalar sua instância própria do GrantID](https://docs.lacunasoftware.com/articles/grant-id/on-premises).
De posse dos parâmetros da sua *subscription* do GrantID, preencha a seção **Oidc**:
* **Authority**: a *OIDC authority* (ex: `https://patorum.grantid.com`)
* **ApiEndpoint**: o endereço da API do GrantID (ex: `https://api.grantid.com`)
* **ApiName**: o escopo de API que será exigido nos tokens de acesso
* **ClientAppId**: o *App-Id* da aplicação frontend
* **AppId**: o *App-Id* da aplicação backend
* **AppSecret**: um segredo para autenticação da aplicação backend
* **RequireHttps** (opcional): atribua o valor `false` caso esteja usando uma instância própria do GrantID sem HTTPS (não recomendado)
## Configurar um *daemon*
Crie o arquivo de definição do serviço:
```sh
touch /etc/systemd/system/lacuna-signer.service
nano /etc/systemd/system/lacuna-signer.service
```
Digite o seguinte:
```ini
[Unit]
Description=Lacuna Signer
[Service]
WorkingDirectory=/usr/share/lacuna-signer
ExecStart=/usr/bin/dotnet Lacuna.Signer.Site.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=lacuna-signer
User=lacuna-signer
Environment=ASPNETCORE_ENVIRONMENT=Linux
Environment=ASPNETCORE_URLS=http://+:5001
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
```
Salve o arquivo, habilite o serviço e inicie-o:
```sh
systemctl enable lacuna-signer
systemctl start lacuna-signer
systemctl status lacuna-signer
```
A saída esperada é semelhante a:
```text
* lacuna-signer.service - Lacuna Signer
Loaded: loaded (/etc/systemd/system/lacuna-signer.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2020-04-15 22:17:50 UTC; 30s ago
Main PID: 2831 (dotnet)
Tasks: 36 (limit: 2319)
CGroup: /system.slice/lacuna-signer.service
└─2831 /usr/bin/dotnet Lacuna.Signer.Site.dll
Apr 15 22:17:50 server.patorum.com systemd[1]: Started Lacuna Signer.
Apr 15 22:17:55 server.patorum.com lacuna-signer[2831]: info: Lacuna.Signer.Site.Startup.RecurringJobsInit[0]
Apr 15 22:17:55 server.patorum.com lacuna-signer[2831]: Initializing recurring jobs
```
Se necessário, reinicie o serviço: `systemctl restart signer`
Para testar se o servidor do Signer está rodando, execute:
```sh
curl http://localhost:5001/api/system/info
```
A saída esperada é algo como:
```json
{"productName":"Lacuna.Signer.Site","productVersion":"1.x.x","timestamp":"..."}
```
## Configurar um servidor proxy reverso
> **Observação**
>
> Se você preferir usar o Apache ao invés do Nginx, [veja este artigo](https://docs.microsoft.com/pt-br/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-2.2#configure-apache).
Instale o Nginx (se ainda não estiver instalado)
```sh
apt-get install nginx
```
Teste a instalação do Nginx:
```sh
curl -I http://localhost/
```
Verifique as primeiras linhas da saída, que devem ser similares a:
```text
HTTP/1.1 200 OK
Server: nginx/...
...
```
Desabilite o site padrão do Nginx:
```sh
rm /etc/nginx/sites-enabled/default
```
Crie um arquivo de configuração para o site do Signer:
```sh
touch /etc/nginx/sites-available/lacuna-signer
nano /etc/nginx/sites-available/lacuna-signer
```
Digite o seguinte, substituindo o valor do item `server_name` pelo domínio de acesso ao site:
```nginx
server {
listen 80;
server_name localhost signer.patorum.com;
client_max_body_size 11000000;
location / {
proxy_pass http://localhost:5001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
> **Dica**
>
> Idealmente, a configuração do site deve conter as entradas `ssl_certificate` e `ssl_certificate_key` com o certificado SSL válido. Essa configuração está fora do escopo dessas
> instruções.
Ative o site:
```sh
ln -sf /etc/nginx/sites-available/lacuna-signer /etc/nginx/sites-enabled/lacuna-signer
```
Teste a configuração do Nginx e recarregue-a:
```sh
nginx -t
nginx -s reload
```
Teste o site:
```sh
curl http://localhost/api/system/info
```
## Veja também
* [Atualização do Signer em Linux](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/troubleshoot)
# [Atualização do Signer em Linux](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/update)
Para atualizar sua [instância *on premises*](https://docs.lacunasoftware.com/articles/signer/on-premises) do [Signer](https://docs.lacunasoftware.com/articles/signer), siga os passos abaixo:
> **Info**
>
> Essas instruções assumem que você está autenticado como **root**. Se você não estiver, execute `sudo su -` antes de continuar!
```sh
curl -O https://cdn.lacunasoftware.com/signer/signer-2.22.1.tar.gz
systemctl stop lacuna-signer
rm -fR /usr/share/lacuna-signer/*
tar xzf signer-2.22.1.tar.gz -C /usr/share/lacuna-signer
chmod -R a=,u+rwX,go+rX /usr/share/lacuna-signer
systemctl start lacuna-signer
```
## Veja também
* [Setup do Signer em Linux](https://docs.lacunasoftware.com/articles/signer/on-premises/linux)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/troubleshoot)
# [Signer - Setup em Azure App Services](https://docs.lacunasoftware.com/articles/signer/on-premises/azure)
Para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/signer/on-premises) do [Signer](https://docs.lacunasoftware.com/articles/signer) em um [Azure App Service](https://docs.microsoft.com/azure/app-service/overview),
siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/signer/on-premises).
## Preparação
Antes de começar, obtenha o pacote de binários:
**[Pacote de binários do Signer](https://cdn.lacunasoftware.com/signer/signer-2.22.1.zip)**
As instruções a seguir assumem que você já tem os seguintes *resources* criados na sua conta do Azure:
* Um *SQL Server* (a criação do *SQL database* é coberta por este artigo)
* Um *App Service Plan* (a criação do App Service é coberta por este artigo)
* Uma zona de DNS referente ao domínio do site
Durante a instalação, serão criados alguns *resources*:
* Um banco de dados (*SQL database*)
* Uma *storage account*
* Um App Service
Sugerimos criar um **resource group** para agrupar os recursos criados. Entretanto, essa é uma medida com propósito meramente de organização. O que
realmente é importante é que **todos os recursos sejam criados na mesma região**. Isso é fundamental para o funcionamento adequado do sistema.
> **Observação**
>
> Os passos abaixo descrevem a criação mais básica de cada recurso. Dependendo da sua infraestrutura você pode querer tomar cuidados
> adicionais de segurança ou resiliência, como por exemplo restringir o acesso a uma rede privada.
## Criação do banco de dados
Siga os procedimentos abaixo para criar um banco de dados para o sistema (você precisa ter um *SQL Server* previamente criado):
1. Na opção **SQL databases**, clique em **+ Create**
1. Na primeira aba (*Basics*), preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
1. Clique na aba **Additional settings**
1. Em **Collation** preencha `Latin1_General_100_CI_AI`
1. Clique em **Review + create**
1. Clique em **Create**
> **Aviso**
>
> A *collation* do banco de dados **PRECISA SER** `Latin1_General_100_CI_AI`. Criar o banco de dados com uma *collation* diferente provavelmente fará com que a instalação falhe!
Uma vez concluída a criação do banco de dados, clique em **Go to resource**. Em seguida, obtenha a *connection string*:
1. Clique em **Connection strings**
1. Tome nota da connection string exibida na seção **ADO.NET (SQL authentication)**
> **Observação**
>
> Substitua na connection string copiada o trecho `{your_password}` pela senha do banco de dados
## Criação de uma *storage account*
Precisamos de uma *storage account* para armazenar arquivos (o sistema armazena arquivos fora do banco de dados para mantê-lo organizado) e também os
logs de sistema. Siga os passos abaixo para criar uma *storage account* (caso queira utilizar uma conta existente, pule essa parte):
1. Em **Storage accounts**, clique em **+ Create**
1. Na primeira aba (*Basics*), preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
* Em *Performance*, escolha **Standard**
* Em *Redundancy*, escolha a opção que preferir (sugerimos a opção **Zone-redundant storage (ZRS)** -- [clique aqui](https://azure.microsoft.com/documentation/articles/storage-redundancy/)
para detalhes sobre as diferentes opções de replicação)
1. Clique em **Review**
1. Clique em **Create**
Uma vez concluída a criação da *storage account*, clique em **Go to resource**. Em seguida, obtenha uma *connection string*:
1. Nas configurações da *storage account*, clique em **Access keys**
1. Na seção **key1**, tome nota do valor do campo **Connection string**
## Criação do App Service
Siga os procedimentos abaixo para criar um *App Service* (você precisa ter um *App Service Plan* previamente criado):
1. Em **App Services**, clique em **+ Create**
1. Na primeira aba (*Basics*), preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
* Em *Publish*, escolha **Code**
* Em *Runtime stack*, escolha **.NET 8 (LTS)**
* Em *Operating System*, escolha o que preferir (o sistema é compatível com Windows e Linux)
1. Clique em **Review + create**
1. Clique em **Create**
Uma vez concluída a criação do App Service, clique em **Go to resource**. Em seguida, tome nota de:
* seu domínio, por exemplo `meu-app-service.azurewebsites.net`
* o **Custom Domain Verification ID**, exibido em **Custom domains**
## Configuração do domínio
Crie o apontamento de DNS para o domínio do site:
> **Observação**
>
> Nas instruções abaixo, utilizaremos como exemplo a criação do apontamento para o domínio `app.patorum.com`
1. Em **DNS zones**, clique na zona à qual o domínio pertence (ex: `patorum.com`)
1. Clique em **+ Record set**
1. Em *Name*, digite a porção mais à esquerda do domínio (ex: `app`)
1. Em *Type*, selecione **CNAME**
1. Em *Alias record set*, selecione **No**
1. Em *Alias*, preencha o domínio do App Service (ex: `meu-app-service.azurewebsites.net`)
1. Clique em **OK**
1. Clique novamente em **+ Record set**
1. Em *Name*, digite `asuid.` seguido da porção mais à esquerda do domínio (ex: `asuid.app`)
1. Em *Type*, selecione **TXT**
1. Em *Value*, cole o valor do **Custom Domain Verification ID** anotado do App Service
1. Clique em **OK**
> **Observação**
>
> Caso você não utilize o Azure como servidor de DNS das zonas, realize o procedimento equivalente no seu servidor de DNS
Após criar o apontamento, adicione o domínio ao *App Service*:
1. De volta às configurações do App Service, clique em **Custom domains**
1. Clique em **+ Add custom domain**
1. Selecione **All other domain services**
1. Deixe selecionadas as opções **App Service Managed Certificate** e **SNI SSL**
1. Em *Domain*, preencha o domínio (ex: `app.patorum.com`)
1. Clique em **Validate**
1. Após a validação, clique em **Add**
## Cópia dos binários
Agora iremos copiar os binários do site. Primeiramente, na seção **Overview** do App Service, pare o serviço clicando em **Stop**.
Em seguida, vá em **Advanced Tools** e clique em **Go**. Você será levado para o painel de controle Kudu do App Service.
1. No menu superior, clique em **Tools**, em seguida em **Zip Push Deploy**
1. Arraste e solte o pacote de binários (arquivo .zip) sobre a lista de arquivos e aguarde o progresso do *deploy*
## Configuração do Signer
Execute o comando abaixo em um Powershell para gerar a chave criptográfica utilizada para cifrar valores sensíveis no banco de dados:
```ps
$k = New-Object byte[] 32;
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($k);
[Convert]::ToBase64String($k);
```
Tome nota do valor gerado.
No App Service, vá em **Configuration** e adicione as seguintes configurações:
* `ASPNETCORE_ENVIRONMENT`: `Azure`
* `General__EncryptionKey`: chave criptográfica gerada acima
* `General__SiteUrl`: URL pública do site, no domínio configurado anteriormente, ex: `https://assinador.patorum.com/`
* `General__SiteName`: nome da sua instância do Signer, ex: *Assinador Patorum*
* `General__SupportEmailAddress`: o endereço de e-mail de suporte (usado no rodapé dos e-mails enviados)
* `General__Theme` (opcional): esquema de cores do site, como um código de três letras, por exemplo `acr` (amazon + cornell-red). Veja os [temas disponíveis](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/temas)
* `AccessControl__IsOpen`: por padrão, o sistema é "fechado", ou seja, exige que usuários sejam previamente cadastrados em uma organização para poderem utilizar o sistema.
Para deixar o sistema "aberto" ou seja, permitir que usuários se registrem e utilizem livremente o sistema (sem aprovação de um administrador), inclua essa configuração com valor `true`. Veja [Controle de acesso](https://docs.lacunasoftware.com/articles/signer/on-premises/access-control)
* `DocumentTypes__Enabled`: por padrão, a seleção de tipo de documento não é exibida ao criar documentos. Para exibi-la, inclua essa configuração com valor `true`
* `ElectronicSignature__Enabled`: por padrão, assinaturas eletrônicas (sem certificado digital) estão desabilitadas. Para habilitá-las, inclua essa configuração com valor `true`
Adicione, também, as configurações descritas nas seções a seguir.
### PKI Suite
Configurações do PKI Suite:
* `PkiSuite__SdkLicense`: sua licença para PKI SDK, no formato Base64 (**obrigatório**)
* `PkiSuite__WebLicense`: sua licença para o componente Web PKI no formato binário (Base64) (**obrigatório**)
* `PkiSuite__LappEnabled`: `true` ou `false`. Indica se a opção de Lapp deve ser habilitada (**opcional**)
### Envio de email
Configurações de envio de email:
* `Email__Enabled`: por padrão, o envio de email está habilitado. Para desabilitar, defina esta configuração como `false` e ignore o restante desta seção.
* `Email__ServerHost`: *hostname* do servidor SMTP
* `Email__EnableSsl`: por padrão, a conversação SMTP é executada por SSL. Para desativar o SSL, defina essa configuração como `false`
* `Email__ServerPort`: Por padrão, a conversação SMTP é realizada pela porta 587. Defina esta configuração para usar uma porta diferente
* `Email__Username` e `Email__Password`: se o servidor SMTP exigir autenticação, defina essas configurações
* `Email__SenderAddress`: endereço de e-mail a ser usado como remetente (campo *From*)
* `Email__SenderName`: nome a ser usado como o nome do remetente (opcional)
### Integração com provedor de OpenID Connect
O Signer requer um provedor de Open ID Connect (OIDC), mais especificamente uma *subscription* do [GrantID](https://docs.lacunasoftware.com/articles/grant-id).
Você pode usar uma *subscription* em nosso serviço SaaS em [grantid.com](https://grantid.com/) ou [instalar sua instância própria do GrantID](https://docs.lacunasoftware.com/articles/grant-id/on-premises).
De posse dos parâmetros da sua *subscription* do GrantID, adicione as seguintes configurações:
* `Oidc__Authority`: a *OIDC authority* (ex: `https://patorum.grantid.com`)
* `Oidc__ApiEndpoint`: o endereço da API do GrantID (ex: `https://api.grantid.com`)
* `Oidc__ApiName`: o escopo de API que será exigido nos tokens de acesso
* `Oidc__ClientAppId`: o *App-Id* da aplicação frontend
* `Oidc__AppId`: o *App-Id* da aplicação backend
* `Oidc__AppSecret`: um segredo para autenticação da aplicação backend
* `Oidc__CpfClaim`: coloque o nome da *claim* de CPF da subscription caso não seja `cpf`
* `Oidc__CustomScopes`: coloque o nome do escopo de CPF da subscription
* `Oidc__RequireHttps` (opcional): atribua o valor `false` caso esteja usando uma instância própria do GrantID sem HTTPS (não recomendado)
> **Dica**
>
> Se a subscription utilizar o CPF como chave, coloque em `Oidc__CpfClaim` e `Oidc__CustomScopes` o valor `preferred_username`
### Blob Storage
Configuração da *storage account*:
* `BlobStorage__ConnectionString`: *connection string* da *storage account* criada anteriormente
* `BlobStorage__ContainerName` (opcional): nome do *container* a ser utilizado para armazenar arquivos. Caso omitido, um container denominado *signer* é utilizado.
### Logging
Configuração de log:
* `Serilog__WriteTo__0__Args__connectionString`: *connection string* da *storage account* criada anteriormente
* `Serilog__WriteTo__0__Args__storageTableName` (opcional): nome da tabela a ser utilizada para armazenar os logs. Caso omitido, uma tabela denominada *SignerLog* é utilizada.
### Connection string
Na seção *Connection strings* (final da página de configurações), clique em **+ New connection string** e preencha:
* **Name**: `DefaultConnection`
* **Value**: valor da connection string obtido durante a criação do banco de dados
* **Type**: escolha **SQLAzure**
Salve as configurações feitas até o momento clicando em **Save**.
## Iniciando o App Service
Por fim, em **Overview** do App Service, clique em **Start**. Em seguida, acesse a URL do site (o primeiro acesso pode demorar alguns instantes).
Clique em **Login** e registre-se. Você então será "promovido" a administrador (o primeiro usuário que se registra é automaticamente promovido),
tendo acesso pleno ao sistema, e a instalação estará concluída.
# [Signer - Setup em Docker](https://docs.lacunasoftware.com/articles/signer/on-premises/docker)
Para a instalação baseada em Docker, a seguinte imagem é disponibilizada no Docker Hub:
**[Imagem Docker do Signer](https://hub.docker.com/r/lacunasoftware/signer)**
Esta imagem requer:
* Uma conexão com um banco de dados SQL Server: a collation do banco de dados deve ser Latin1_General_100_CI_AI ou Latin1_General_CI_AI.
* Armazenamento (compartilhado entre todas as instâncias da imagem): veja as configurações de BlobStorage abaixo para mais informações.
* Servidor OIDC ([GrantID](https://docs.lacunasoftware.com/articles/grant-id)) em nuvem ou on-premises.
## Configuração básica
O container desta imagem é configurado por meio de variáveis de ambiente.
Os nomes das variáveis de ambiente devem seguir o padrão `Section__Setting`. Por exemplo: para configurar o SupportEmailAddress
da seção General, defina uma variável com o nome `General__SupportEmailAddress`.
As configurações obrigatórias são apresentadas abaixo.
### Connection string do banco de dados
Na seção **ConnectionStrings**:
* **DefaultConnection**: defina a connection string do banco de dados. Uma connection string típica é assim:
```text
Data Source=SERVER;Initial Catalog=DATABASE;User ID=USERNAME;Password=PASSWORD
```
> **Observação**
>
> Se você criou o banco de dados usando recursos avançados, como log shipping ou mirroring, sua connection string pode ser diferente
Exemplo:
```sh
ConnectionStrings__DefaultConnection=Server=SERVER;Initial Catalog=DATABASE;User ID=USERNAME;Password=PASSWORD;
```
> **Observação**
>
> A maioria dos endpoints de servidor exige o seguinte formato para o parâmetro Data Source/Server:
```sh
tcp:,
```
### Seção BlobStorage
Define como a aplicação armazena e recupera arquivos.
Veja [Configuração do Blob Storage](https://docs.lacunasoftware.com/articles/signer/on-premises/blob-storage) para detalhes e exemplos dos serviços suportados.
### Seção Serilog
Define onde os logs da aplicação são armazenados.
Veja [Configuração do Serilog](https://docs.lacunasoftware.com/articles/signer/on-premises/serilog) para detalhes e exemplos dos serviços suportados.
### Configuração do OpenID Connect
A seção **Oidc** configura o servidor OpenID Connect, necessário para o gerenciamento de usuários. É necessária uma *subscription* do [GrantID](https://docs.lacunasoftware.com/articles/grant-id).
Você pode usar uma *subscription* SaaS em [grantid.com](https://grantid.com/) ou executar sua própria instância do GrantID.
* **Authority**: a authority OIDC (ex.: *https://patorum.grantid.com*)
* **ApiEndpoint**: o endpoint da API do servidor OIDC (ex.: *https://api.grantid.com*)
* **ApiName**: o escopo de API que será exigido nos tokens de acesso
* **ClientAppId**: o *client id* do aplicativo do painel
* **AppId**: o *client id* do aplicativo de backend
* **AppSecret**: o *client secret* do aplicativo de backend
* **RequireHttps** (opcional): defina como `false` se o servidor OIDC não usar HTTPS
Veja a página [Configurando uma aplicação SPA](https://docs.lacunasoftware.com/articles/grant-id/on-premises/configure-spa-app) para saber como obter esses valores.
Exemplo:
```sh
Oidc__ApiEndpoint=https://api.grantid.com
Oidc__ApiName=myapp-api
Oidc__AppId=myapp-backend
Oidc__AppSecret=8CqeGeur46k...
Oidc__Authority=https://mysubscription.grantid.com
Oidc__ClientAppId=myapp
Oidc__CpfClaim=cpf
Oidc__CustomScopes=mysub-cpf
```
### PKI Suite
Na seção **PkiSuite**:
* **SdkLicense**: sua licença do PKI SDK, no formato Base64 (**obrigatório**)
* **WebLicense**: sua licença do componente Web PKI no formato binário (Base64). Necessária apenas se os usuários forem emitir certificados em seus computadores (procedimento de emissão pela web)
* **WebBrand**: se você tiver uma *setup brand* personalizada no Web PKI, defina-a aqui
Exemplo:
```sh
PkiSuite__SdkLicense=AxAAzHeWJY9AK...
PkiSuite__WebLicense=ApwBY29uZml1A...
```
### Configurações gerais
* **SiteUrl**: a URL a partir da qual a aplicação será executada.
* **SiteName**: o nome da aplicação.
* **SupportEmailAddress**: o e-mail de suporte, que será incluído no rodapé de todos os e-mails enviados pela aplicação.
Exemplo:
```sh
General__SiteUrl=https://signer.myapp.com
General__SiteName=MyApp Signer
General__SupportEmailAddress=support@email.com
```
### Configuração de bindings
* **UseReverseProxy**: defina como `true` se o container for executado atrás de um proxy reverso ou balanceador de carga. Caso contrário, pode ser omitida.
```sh
Bindings__UseReverseProxy=true
```
### Envio de e-mail
Na seção **Email**:
* **Enabled**: por padrão, o envio de e-mail está habilitado. Para desabilitá-lo, defina esta configuração como `false` e ignore o restante desta seção
* **ServerHost**: hostname do servidor SMTP
* **EnableSsl**: por padrão, a conversação SMTP é feita sobre SSL. Para desabilitar o SSL, defina esta configuração como `false`
* **ServerPort**: por padrão, a conversação SMTP é feita pela porta 587. Defina esta configuração para usar outra porta
* **Username** e **Password**: se o servidor SMTP exigir autenticação, defina estas configurações
* **SenderAddress**: endereço de e-mail a ser usado como remetente (campo *from*)
* **SenderName**: nome a ser usado como nome do remetente (opcional)
Exemplo:
```sh
Email__ServerHost=email-smtp.us-east-1.amazonaws.com
Email__Username=USERNAME
Email__Password=PASSWORD
Email__SenderAddress=no-reply@email.com
Email__SenderName=MYAPPNAME
```
## Configurações adicionais
Outras configurações podem ser encontradas na página [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings).
## Instalação
Baixe a imagem estável mais recente, configure as variáveis de ambiente obrigatórias e execute o container.
> **Aviso**
>
> Comece com **apenas um container**, pois na primeira inicialização as tabelas do banco de dados são criadas (mais containers poderiam causar uma condição de corrida).
> Depois que a inicialização inicial for concluída, você pode executar quantos containers quiser.
## Orientações para atualização
Antes de atualizar seu container, recomendamos consultar o [Changelog](https://docs.lacunasoftware.com/articles/signer/changelog) para ver o que mudou da sua
versão atual até a mais recente disponível.
Se alguma das versões incluídas na atualização tiver alterações no modelo do banco de dados ("Atualiza modelo de banco de dados: sim"),
proceda com cuidado, pois o container tentará atualizar o banco de dados na inicialização.
Nesse cenário, recomendamos escolher uma das seguintes opções:
* Reduzir o número de containers em execução para 1.
* Permitir que apenas um container atualize o banco de dados. Para isso, adicione as seguintes configurações a todos os containers, exceto um:
```sh
General__ProcessBackgroundJobs=false
General__AutoUpdateDatabase=false
```
Por fim, para atualizar, basta baixar a imagem com a tag correspondente à versão desejada e executar o container.
## Veja também
* [Signer on-premises](https://docs.lacunasoftware.com/articles/signer/on-premises)
# [Preparando o banco de dados para instalação do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/prepare-database)
Para instalar uma [instância *on premises*](https://docs.lacunasoftware.com/articles/signer/on-premises) do [Signer](https://docs.lacunasoftware.com/articles/signer), você vai precisar de uma *connection string* para um **banco de dados do SQL Server** com:
* Collation: `Latin1_General_100_CI_AI`
* Credenciais correspondentes a um usuário com papel `db_owner`
> **Aviso**
>
> A *collation* do banco de dados **PRECISA SER** `Latin1_General_100_CI_AI`. Criar o banco de dados com uma *collation* diferente provavelmente fará com que a instalação falhe!
Se você precisar de ajuda para preparar o banco de dados, siga os passos neste artigo.
> **Observação**
>
> Você não precisa seguir estas instruções especifícas. Se você desejar preparar o banco de dados,
> por exemplo usando recursos avançados como *log shipping* ou *mirroring*, você pode fazê-lo, desde que a *collation* e as permissões sejam observadas.
## Criando o banco de dados
Para criar o banco de dados, use a seguinte consulta T-SQL (opcionalmente alterando o nome do banco de dados):
```sql
USE Master;
CREATE DATABASE Signer COLLATE Latin1_General_100_CI_AI;
GO
```
## Criando as credenciais
Crie um usuário e associe-o ao papel `db_owner`:
```sql
USE master;
CREATE LOGIN SignerAdm WITH PASSWORD = 'XXXXX';
GO
USE Signer;
CREATE USER SignerAdm FOR LOGIN SignerAdm;
EXEC sp_addrolemember 'db_owner', 'SignerAdm';
GO
```
A *connection string* seria, então:
```text
Data Source=.;Initial Catalog=Signer;User ID=SignerAdm;Password=XXXXX
```
> **Observação**
>
> Essa *connection string* presume que o servidor do banco de dados esteja instalado no mesmo servidor que o aplicativo da web. Se este não for o caso,
> o valor após `Data Source=` deve ser alterado.
# [Configuração do Serilog](https://docs.lacunasoftware.com/articles/signer/on-premises/serilog)
Define um ou mais sinks para onde os logs devem ser direcionados.
## File
Fornecido pelo pacote [Serilog File Sink](https://github.com/serilog/serilog-sinks-file).
Exemplo:
```sh
Serilog__WriteTo__0__Name=File
Serilog__WriteTo__0__Args__path=/var/logs/myapp.log
Serilog__WriteTo__0__Args__outputTemplate={Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}
```
## Azure Table Storage
Fornecido pelo pacote [Serilog.Sinks.AzureTableStorage](https://github.com/serilog/serilog-sinks-azuretablestorage).
Exemplo:
```sh
Serilog__WriteTo__0__Name=AzureTableStorageWithProperties
Serilog__WriteTo__0__Args__storageTableName=MyAppLogTable
Serilog__WriteTo__0__Args__connectionString=DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net
Serilog__WriteTo__0__Args__keyGenerator=Lacuna.Spa.Engine.AzureTableKeyGenerator, Lacuna.Spa.Engine
```
* O parâmetro **keyGenerator** deve ser definido exatamente como no exemplo (com o valor `Lacuna.Spa.Engine.AzureTableKeyGenerator, Lacuna.Spa.Engine`).
## Amazon S3
Fornecido pelo pacote [Serilog.Sinks.AmazonS3](https://github.com/SeppPenner/Serilog.Sinks.AmazonS3).
Exemplo:
```sh
Serilog__WriteTo__0__Name=AmazonS3
Serilog__WriteTo__0__Args__path=myapplog.txt
Serilog__WriteTo__0__Args__bucketName=myappbucket
Serilog__WriteTo__0__Args__bucketPath=logs
Serilog__WriteTo__0__Args__serviceUrl=https://s3.us-east-1.amazonaws.com
Serilog__WriteTo__0__Args__awsAccessKeyId=MYACCESSKEYID
Serilog__WriteTo__0__Args__awsSecretAccessKey=MYSECRETACCESSKEY
Serilog__WriteTo__0__Args__autoUploadEvents=true
```
## New Relic Logs
Fornecido pelo pacote [serilog-sinks-newrelic-logs](https://github.com/ThiagoBarradas/serilog-sinks-newrelic-logs).
Exemplo:
```sh
Serilog__Enrich__3=WithNewRelicLogsInContext
Serilog__WriteTo__0__Name=NewRelicLogs
Serilog__WriteTo__0__Args__applicationName=myappname
Serilog__WriteTo__0__Args__licenseKey=MYLICENSE
```
# [Configuração do Blob Storage](https://docs.lacunasoftware.com/articles/signer/on-premises/blob-storage)
A seção de configuração **BlobStorage** define como uma aplicação armazena e recupera arquivos.
## Sistema de arquivos
Armazena os arquivos no sistema de arquivos. Configurações disponíveis:
* **Path**: o caminho onde os arquivos serão armazenados.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=FileSystem
BlobStorage__Path=/var/files/myapp
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=FileSystem
Path=/var/files/myapp
```
Exemplo (configuração JSON):
```json
{
...,
"BlobStorage": {
"Type": "FileSystem"
"Path": "/var/files/myapp"
},
...
}
```
## Azure Storage Account
Armazena os arquivos em um container de uma Azure Storage Account. Configurações disponíveis:
* **ConnectionString**: a connection string da Storage Account.
* **ContainerName**: o nome do container onde os arquivos serão armazenados.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=Azure
BlobStorage__ConnectionString=DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net
BlobStorage__ContainerName=myapp-container
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=Azure
ConnectionString="DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net"
ContainerName=myapp-container
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "Azure",
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net",
"ContainerName": "myapp-container"
},
...
}
```
## AWS S3 (Simple Storage Service)
Armazena os arquivos em um bucket do S3. Configurações disponíveis:
* **Region**: a região do bucket.
* **BucketName**: o nome do bucket onde os arquivos serão armazenados.
* **AccessKey**: o ID da chave de acesso de um usuário do IAM que tenha acesso ao bucket.
* **SecretKey**: a chave de acesso secreta de um usuário do IAM que tenha acesso ao bucket.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=AwsS3
BlobStorage__Region=us-east-1
BlobStorage__BucketName=myappbucket
BlobStorage__AccessKey=MYACCESSKEYID
BlobStorage__SecretKey=MYSECRETACCESSKEY
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=AwsS3
Region=us-east-1
BucketName=myappbucket
AccessKey=MYACCESSKEYID
SecretKey=MYSECRETACCESSKEY
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "AwsS3",
"Region": "us-east-1",
"BucketName": "myappbucket",
"AccessKey": "MYACCESSKEYID",
"SecretKey": "MYSECRETACCESSKEY"
},
...
}
```
### Armazenamento de objetos compatível com S3
Também é possível usar um serviço de armazenamento de objetos compatível com S3 hospedado por você, também conhecido como "clone do S3", por exemplo o [minIO](https://min.io/). Nesse caso, em vez de
*Region*, especifique o **Endpoint**.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=AwsS3
BlobStorage__Endpoint=http://localhost:9000
BlobStorage__BucketName=myappbucket
BlobStorage__AccessKey=MYACCESSKEYID
BlobStorage__SecretKey=MYSECRETACCESSKEY
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=AwsS3
Endpoint=http://localhost:9000
BucketName=myappbucket
AccessKey=MYACCESSKEYID
SecretKey=MYSECRETACCESSKEY
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "AwsS3",
"Endpoint": "http://localhost:9000",
"BucketName": "myappbucket",
"AccessKey": "MYACCESSKEYID",
"SecretKey": "MYSECRETACCESSKEY"
},
...
}
```
Por padrão, quando um armazenamento compatível com S3 é configurado, é usado o endereçamento no estilo de caminho (path-style), em vez do endereçamento padrão no estilo de host virtual
(virtual-hosted–style) usado quando um bucket padrão do AWS S3 é configurado. Isso pode ser alterado com a configuração **ForcePathStyle** (`true` ou `false`). Para mais informações sobre o endereçamento do S3, veja
[Virtual hosting of buckets](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html).
## Google Cloud Storage
Armazena os arquivos em um bucket do Google Cloud Storage. Você precisará de uma conta de serviço com o papel Storage Object Admin. Configurações disponíveis:
* **BucketName**: o nome do bucket onde os arquivos serão armazenados.
* **ProjectId**: o ID do projeto do Google Cloud.
* **PrivateKeyId**: o ID da chave privada da conta de serviço.
* **PrivateKey**: a chave privada da conta de serviço.
* **ClientEmail**: o e-mail da conta de serviço.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=Gcp
BlobStorage__BucketName=myappbucket
BlobStorage__ProjecId=my-app-project-id
BlobStorage__PrivateKeyId=MYPRIVATEKEYID
BlobStorage__PrivateKey=MYPRIVATEKEY
BlobStorage__ClientEmail=myserviceaccountemail@my-app-project-id.iam.gserviceaccount.com
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=Gcp
BucketName=myappbucket
ProjecId=my-app-project-id
PrivateKeyId=MYPRIVATEKEYID
PrivateKey=MYPRIVATEKEY
ClientEmail=myserviceaccountemail@my-app-project-id.iam.gserviceaccount.com
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "Gcp",
"BucketName": "myappbucket",
"ProjecId": "my-app-project-id",
"PrivateKeyId": "MYPRIVATEKEYID",
"PrivateKey": "MYPRIVATEKEY",
"ClientEmail": "myserviceaccountemail@my-app-project-id.iam.gserviceaccount.com"
},
...
}
```
### Obtendo as configurações do Google Cloud Storage
Primeiro, precisamos de um bucket do Google Cloud Storage para armazenar os arquivos. Um novo bucket pode ser criado pelo botão **Create** no console do Google Cloud Storage:

Escolha um nome para o novo bucket e clique em continuar:

Escolha a região do novo bucket:

Garanta que a prevenção de acesso público e o controle de acesso uniforme estejam aplicados ao novo bucket:

Escolha a política de retenção de objetos e crie o bucket:

Pode aparecer um diálogo para confirmar que o acesso público será impedido. Clique em confirmar para continuar:

Em seguida, precisamos criar uma conta de serviço do Google Cloud para acessar o bucket. Isso pode ser feito pelo botão **Create service account** na página *Service Accounts*, em *IAM & Admin*:

Também precisamos de um nome para a nova conta de serviço. Escolha-o e clique em **Create and continue**:

Garanta que a nova conta de serviço receba o papel Storage Object Admin:

Conceda acesso a principals, se necessário, e clique em concluir para finalizar a criação da nova conta de serviço:

Agora que a conta de serviço foi criada, precisamos de uma chave para autenticá-la. Clique na nova conta de serviço para abrir a página de detalhes:

Nos detalhes da conta de serviço, vá para a seção de chaves:

Na opção **Add key**, escolha **Create new key**:

Escolha o formato JSON e clique no botão **Create**:

Será baixado um arquivo JSON contendo a chave privada e outras informações relacionadas à conta de serviço:

No arquivo JSON, você encontrará os campos `project_id`, `private_key_id`, `private_key` e `client_email` para configurar o serviço de Blob Storage.
> **Info**
>
> As informações deste arquivo JSON permitem acessar os recursos do GCP e não podem ser comprometidas! Armazene ou descarte o arquivo com segurança.
# [Controle de acesso do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/access-control)
Esta página descreve as possíveis configurações de controle de acesso dos usuários do Signer.
## Sistema Aberto
No sistema Aberto, qualquer usuário pode acessar sua instância, se cadastrar e receber automaticamente uma conta Pessoal. Todos documentos
que forem criados por esse usuário ficam associados a essa conta.
Adicionalmente o usuário pode criar Organizações que podem representar Empresas, Departamentos ou Grupos de trabalho.
## Sistema Fechado
No sistema Fechado, um usuário qualquer continua podendo se cadastrar, mas somente terá acesso às funcionalidades do sistema caso ele seja
adicionado manualmente por um administrador em uma Organização. Caso contrário, ao fazer login será apresentada a tela de acesso restrito:

O administrador deve acessar a Área de Administração para cadastrar os usuários que poderão utilizar o sistema:

A partir da v1.38.0, é possível configurar um e-mail para solicitação de acessos. Dessa forma, quando o usuário acessar a tela de acesso restrito, aparecerá
uma opção para solicitar acesso. Ao selecioná-la, o sistema enviará uma notificação para o endereço de e-mail configurado. O usuário, que deverá ser um administrador
da instância, deverá adicionar o solicitante em uma organização caso a solicitação seja válida.

## Perfis básicos
Tanto para o sistema Aberto ou Fechado, é possível definir quais permissões básicas serão disponibilizadas para os usuários que se cadastrarem no sistema.
As permissões configuráveis são:
1. Criação de documentos
1. Criação de organizações*
Por padrão, essas duas permissões já são concedidas a todos usuários. No entanto, se isso não for desejado para seu caso de uso, elas podem ser desabilitadas por
configuração de instância. Nesse caso, você deverá dar essas permissões manualmente para cada usuário, concedendo um ou mais dos perfis exibidos abaixo:
1. Criador de documentos
1. Criador de organizações*
Esses perfis devem ser configurados pelo administrador da instância no menu Usuários > Detalhes do Usuário > Editar Acesso do Usuário:

Um exemplo comum, é a necessidade de disponibilizar as funcionalidades básicas de visualização de documentos, relatórios e assinatura em lote para os assinantes
dos documentos sem permitir que eles enviem novos documentos ou criem organizações.
> **Aviso**
>
> O papel de criador de organizações é ignorado no sistema Fechado, pois nessa modalidade a criação de organizações é sempre responsabilidade do administrador da instância.
# [Autenticação com Pix no Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/pix-authentication)
O Signer permite a realização de assinaturas eletrônicas autenticando o usuário através de um pagamento realizado com a tecnologia [Pix](https://www.bcb.gov.br/estabilidadefinanceira/pix).
Para a geração dos QR codes Pix, é utilizada a integração com as APIs do [Gerencianet](https://gerencianet.com.br/).
## Configuração da conta
Para esta integração é preciso de uma conta do Gerencianet. Você pode abrir uma conta nova baixando o aplicativo deles no Google Play ou App Store.
Para habilitar a integração, você precisará das seguintes informações:
1. Credenciais de API: `Client ID` e `Client Secret`.
1. Certificado de autenticação.
1. Chave Pix.
### Credenciais de API
Uma vez criada sua conta, acesse a interface Web e, no menu principal, selecione a opção API:

No menu lateral, selecione a opção `Minhas aplicações` e clique no botão `Nova Aplicação`:

1. No nome da aplicação insira o nome da sua instância.
1. Desative a API de emissões (Boletos e Carnês).
1. Ative a API Pix e habilite os seguintes escopos de produção e homologação:
* `Alterar Cobrança`
* `Consultar Cobrança`
* `Consultar Payload`
* `Alterar Pix`
* `Consultar Pix`
* `Enviar Pix`

Os valores `Client ID` e `Client Secret` serão exibidos na aba Produção da aplicação criada.
### Certificado de autenticação
Ainda na interface web, no menu lateral, selecione a opção `Meus certificados`.
Na aba Produção, clique no botão `Novo Certificado`.
Informe o nome do certificado usando o nome da sua instância. Use o caractere `-` ao invés de espaços (se houver).
Baixe o certificado e armazene em um local seguro.
> **Aviso**
>
> Conforme é exibido na mensagem após a criação, o certificado não poderá ser obtido novamente.
### Chave Pix
No aplicativo utilizado para criar a conta, faça o login e entre na área Pix.
Selecione `Minhas Chaves` e clique no botão `+` para cadastrar uma chave Pix.
Selecione o tipo `Chave aleatória` e conclua a operação.
# [Integração com Zoom no Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/zoom)
O Signer permite integração com [Zoom](https://zoom.us/) para realizar e armazenar videoconferências relacionadas a documentos.
## Configuração da conta
Para esta integração é preciso de uma conta do Zoom com plano igual ou superior ao Profissional.
Além disso, as configurações da conta devem permitir o recurso `Gravação em nuvem` conforme as imagens abaixo:


Nesta integração, cada usuário/licença da conta é considerado como uma sala de reunião. Quando um usuário do Signer solicita a criação
de uma conferência, o Signer lista todos os usuários do Zoom e tenta encontrar um que não esteja em uma conferência no momento. Por
isso, caso deseje realizar conferências simultâneas, é importante ter mais de um usuário/licença na conta do Zoom.
> **Dica**
>
> Ao configurar os usuários da conta, não esqueça de atribuir-lhes a licença, caso contrário conferências realizadas
> com aquele usuário não terão o vídeo salvo na nuvem.
## Obtenção de credenciais
Para habilitar a integração no assinador é preciso obter uma credencial que permite que o Assinador chame as APIs do Zoom.
Para isso siga o procedimento abaixo:
1. Após fazer o login na sua conta, acesse [https://marketplace.zoom.us/](https://marketplace.zoom.us/);
1. Abra o menu `Develop` no canto superior direito da tela;
1. Selecione a opção `Build App` dentro do seletor que se abriu;
1. Selecione a opção `Server-to-Server OAuth`:

1. Dê um nome para seu aplicativo (pode ser o mesmo nome de sua instância do Assinador);

1. Selecione o painel `Information`;
1. Preencha as informações básicas e as de contato do desenvolvedor;
1. Selecione o painel `Scopes`;
1. Selecione os seguintes:
- Meeting:
- View a meeting (meeting:read:meeting:admin)
- Create a meeting for a user (meeting:write:meeting:admin)
- View a user's meetings (meeting:read:list_meetings:admin)
- Recording:
- Returns all of a meetings's recordings (cloud_recording:read:list_recording_files:admin)
- View a recording (cloud_recording:read:recording:admin)
- User:
- View users (user:read:list_users:admin)
- View a user (user:read:user:admin)
- Dashboard:
- View meeting participants' metrics (dashboard:read:list_meeting_participants:admin)
1. Selecione o painel `App Credentials`;
1. Copie as informações necessárias para a integração: `AccountId`, `ClientId`, e `ClientSecret`.

## Funcionamento
Para criar uma conferência, acesse a tela de detalhes de um documento e clique no botão iniciar conferência:

> **Observação**
>
> Somente o criador do documento (para conta pessoal) ou alguém com permissão de gerente ou administrador (para conta de organização)
> pode realizar essa ação.
Em seguida, serão exibidos dois links: o primeiro para acessar como o anfitrião (host) da conferência e outro para enviar/compartilhar
com os participantes:

No Zoom, ao final da conferência o anfitrião (host) deve utilizar a opção `End Meeting for All`:

Uma vez encerrada a reunião, o vídeo da reunião aparecerá na tela de detalhes do documento:

> **Observação**
>
> O tempo para ser disponibilizado o vídeo varia em função do tamanho da reunião. Para reuniões pequenas demora cerca de 15 a 20 minutos.
> Para reuniões grandes pode demorar até 24 horas.
# [Pagamentos no Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/payments)
Esta página descreve as opções para obtenção de pagamentos relativos às faturas geradas pelo Sistema de Cobranças.
## Sistema de Cobranças
O Signer possui um Sistema de Cobranças flexível que permite ao administrador da instância escolher quais serviços
serão tarifados: documento, assinatura, aprovação etc. Caso não esteja visualizando na sua instância as opções
descritas aqui verifique se o módulo de cobrança foi habilitado.
O Sistema de Cobranças trabalha com a geração de faturas mensais sem data de duração, isto é, após a virada do mês
a fatura é fechada e fica disponível para consulta/pagamento de um usuário ou organização.
O administrador da instância pode escolher se o não pagamento das faturas irá bloquear ou não os usuários/organizações.
As opções disponíveis para processamento de pagamentos estão descritas abaixo:
### Operação Simplificada
Para instâncias com número pequeno de usuários/organizações, a gerência de pagamentos pode ser feita externalmente ao
sistema.
Nesse modelo, fica a cargo do administrador da instância verificar as faturas fechadas, notificar o cliente responsável
e processar o pagamento de cada fatura.
Uma vez identificado que o pagamento foi realizado, a fatura pode ser consultada e seu status alterado na tela de
detalhes da fatura pelo administrador:

### Operação via Integração
Para instâncias com um grande número de usuários/organizações, recomenda-se que a gerência de pagamentos seja feito de maneira
automatizada.
O primeiro passo para automatizar o processo é configurar o webhook de cobrança da instância. Isso deve ser feito em
Administração > Cobrança > Integração:

Uma vez configurado o Webhook, o endereço especificado receberá uma requisição POST após o fechamento de cada fatura.
Segue abaixo um exemplo desta requisição:
```json
{
"type": "InvoiceClosed",
"data":
{
"id": 126,
"month": 9,
"year": 2020,
"value": 5,
"invoiceTotals": [
{
"transactionType": "Document",
"price":
{
"transactionType": "Document",
"pricingType": "Simple",
"price": 0.5,
"priceRanges": null
},
"total": 10,
"value": 5
},
{
"transactionType": "ElectronicSignature",
"price":
{
"transactionType": "ElectronicSignature",
"pricingType": "NoCharge",
"price": null,
"priceRanges": null
},
"total": 1,
"value": 0
},
{
"transactionType": "Signature",
"price":
{
"transactionType": "Signature",
"pricingType": "Range",
"price": null,
"priceRanges": [
{
"limit": 10,
"price": 0
},
{
"limit": 20,
"price": 0.5
},
{
"limit": null,
"price": 0.25
}
]
},
"total": 8,
"value": 0
}
],
"organization":
{
"id": "6a49d03c-5f3f-428c-8221-8474366f98d0",
"name": null,
"identifier": null,
"owner":
{
"id": "12c1d41a-77b2-404e-a155-84cc7d0f93e8",
"name": null
}
},
"billingInformation":
{
"type": "Individual",
"individual":
{
"name": "Danilo Igor das Neves",
"identifier": "57972418624"
},
"company": null,
"contactName": "",
"email": "daniloneves@mailinator.com",
"phone": "+55 (84) 3619-9479",
"streetAddress": "Rua Principal, s/n",
"addressNumber": "696",
"additionalAddressInfo": "Apto 110",
"neighborhood": "Bom Descanso",
"address": "",
"address2": "",
"zipCode": "59430972",
"city": "Lagoa de Velhos",
"state": "RN"
}
}
}
```
Para a referência completa da requisição veja [o schema Webhooks.InvoiceClosedModel no Swagger](https://www.dropsigner.com/swagger/index.html#model-Webhooks.InvoiceClosedModel).
Para exemplos de código, veja os links abaixo:
* [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/InvoiceWebhookHandlingScenario.cs)
* [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/InvoiceWebhookHandlingScenario.java)
* [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/InvoiceWebhookHandlingScenario.php)
> **Observação**
>
> O Webhook pode ser utilizado também na Operação Simplificada.
O sistema que receber o Webhook deve processar o evento da fatura e gerar a cobrança correspondente notificando o email especificado
nos dados de cobrança.
Ao detectar o pagamento da fatura, ele deve enviar uma requisição ao Signer para informar que a fatura foi paga, conforme definido
abaixo:
```javascript
PUT /api/invoices/{id}/payment
{
"isPaid": true
}
```
Para exemplos de código, veja os links abaixo:
* [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/InvoiceWebhookHandlingScenario.cs)
* [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/InvoiceWebhookHandlingScenario.java)
* [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/InvoiceWebhookHandlingScenario.php)
> **Aviso**
>
> Para esta requisição será necessária uma chave de API com privilégios de administração de cobrança no sistema.
#### Criação de chave de API de administração de cobrança
Para gerá-la crie uma aplicação selecionando a organização Sys Admin:


Em seguida gere a chave na opção "Chaves":



### Operação via Gateway de pagamentos iugu
É possível integrar o Signer diretamente com o Gateway de Pagamentos da iugu, permitindo que os próprios
usuários/organizações paguem as faturas com cartão de crédito, boleto bancário ou Pix. Veja a página
[Gateway de pagamentos iugu](https://docs.lacunasoftware.com/articles/signer/on-premises/iugu).
> **Observação: Nota fiscal**
>
> Para emissão de NFS-e das faturas pagas, veja [Nota Fiscal (NFS-e)](https://docs.lacunasoftware.com/articles/signer/on-premises/nfse).
# [Gateway de pagamentos iugu](https://docs.lacunasoftware.com/articles/signer/on-premises/iugu)
A partir da versão [1.27.0](https://docs.lacunasoftware.com/articles/signer/changelog#v1-27-0), é possível integrar o Signer diretamente com o Gateway de Pagamentos da [iugu](https://www.iugu.com/). Desta forma,
depois que uma fatura é fechada, os próprios usuários/organizações podem realizar o pagamento com cartão de crédito, boleto bancário ou Pix.
## Configuração no painel de controle da iugu
Para contratar a iugu, é preciso escolher um [plano de assinatura](https://www.iugu.com/planos/). Cada plano traz funcionalidades, mudanças nas tarifas e custos por transação que
devem ser avaliados pelo administrador da instância, mas o Signer é capaz de funcionar plenamente com o plano mais básico ("Conheça a iugu").
Após realizar [cadastro na iugu](https://auth.iugu.com/new_user?service=https%3A%2F%2Falia.iugu.com%2F), é preciso acessar o [painel de controle](https://alia.iugu.com/) e gerar
um API Token. Para isso, acesse `Configurações`> `Integração via API` e clique no botão Novo:

O tipo do token deve ser `Produção`. Adicione também uma descrição, exemplo: "API Signer Prod".

Em seguida, obtenha o ID de sua conta acessando `Configurações`> `Informações gerais`. Abaixo de `CONTA` será exibido o ID da sua conta na iugu.

O próximo passo é configurar o Webhook para notificar o Signer sobre os pagamentos. Ainda na tela de configurações, acesse `Comunicação via Gatilhos` e depois clique no botão Novo:

O campo URL deve ser preenchido com o endereço de sua instância do Signer seguido do caminho padrão de webhooks conforme o exemplo abaixo:
```text
https://seu-signer.com.br/api/webhooks/iugu/invoice/changed
```
Caso ocorra erro ao tentar registrar o webhook, adicione `?noecho` ao final da URL.
O campo autorização deve ser preenchido da seguinte forma:
```javascript
Bearer WebhooksAuthKey
```
`WebhooksAuthKey` pode ser qualquer valor, mas recomendamos que seja gerada uma string aleatória de alta entropia. Entre em contato conosco para receber instruções de como
gerar esse valor.
> **Aviso**
>
> * O webhook deve ser gerado no ambiente de produção (o ambiente selecionado é exibido no topo da página).
> * O campo de autorização deve obrigatoriamente iniciar com `Bearer ` como mostrado no exemplo.
> * O evento selecionado deve ser `Mudança de estado de Fatura`.

Em seguida, desabilite a cobrança automática feita diretamente pela iugu, pois o Signer já possui seu próprio sistema de cobrança. Acesse a opção `Recebimento`>
`Régua de Cobrança` e clique em `Alterar fluxo de cobrança`.

Deixe somente o fluxo "Expira a fatura" com 5 dias após o vencimento.

> **Observação**
>
> A opção "Expira a fatura" é um mecanismo da iugu para marcar uma fatura como expirada após alguns dias depois do vencimento. É recomendado pelo menos 5 dias, para que pagamentos
> com boletos que tenham sido feitos até o vencimento possam ter 5 dias para compensação.
O último passo é conferir se todos os meios de pagamento: boleto bancário, cartão de crédito e Pix estão habilitados. Acesse `Configurações`> `Recebimentos` e selecione
cada um dos métodos de pagamentos listados, marcando a opção "Ativo" e clicando no botão "Salvar":

## Configuração no Signer
Usuários e organizações que já tinham dados de faturamento cadastrados antes da versão [1.27.0](https://docs.lacunasoftware.com/articles/signer/changelog#v1-27-0), precisarão informar os dados novamente para que
sejam realizadas novas validações das informações conforme necessidades da iugu.

Após os dados de faturamento serem submetidos novamente, o usuário poderá definir um método de pagamento padrão para sua conta pessoal ou de organização:

> **Observação**
>
> O método de pagamento padrão para cartões de crédito também pode ser escolhido no momento de pagamento de uma fatura.
Os meios de pagamentos disponíveis para o usuário/organização são Pix, boleto bancário e cartão de crédito. Para cartões de crédito, é possível selecionar um que já tenha sido
cadastrado ou cadastrar um novo:

> **Observação**
>
> As bandeiras aceitas para pagamentos com cartão de crédito são:
> * American Express
> * Diners
> * Elo
> * MasterCard
> * Visa
Quando uma fatura é fechada e o usuário/organização poderá pagar aquela fatura na tela de detalhes conforme abaixo:


Após o pagamento, o status da fatura é atualizada com o método de pagamento utilizado, dia e horário.

## Falhas de pagamento com cartão de crédito
O pagamento de uma fatura com cartão de crédito pode ser negado por diversas causas. Um código de erro será exibido no momento do pagamento e pode ser consultado nessa
[lista de erros](https://support.iugu.com/hc/pt-br/articles/206858953-Como-identificar-o-erro-da-tentativa-de-pagamento-).

> **Aviso**
>
> Em alguns casos, é possível que o proprietário do cartão receba via SMS ou no APP do cartão, a informação de cobrança realizada com sucesso, porém, caso a fatura
> do usuário/organização não conste como PAGA, este lançamento de cobrança é automaticamente estornado na fatura do cartão, dentro de 7 a 10 dias úteis.
## Cobrança automática
O Signer possui um sistema de cobrança automática para cartão de crédito que é feito quando o usuário/organização salva um cartão de crédito como método de pagamento padrão.
A cobrança automática é agendada para a data de vencimento da fatura, mas a cobrança pode demorar um ou dois dias adicionais.
No entanto, existem alguns casos em que a cobrança automática não será feita:
* Se o método de pagamento padrão do usuário/organização no momento de fechamento da fatura não for cartão de crédito.
* Se a fatura for paga antes do dia de vencimento.
* Se o usuário/organização trocar o método de pagamento da fatura para boleto bancário. Caso o boleto seja gerado, a cobrança automática será cancelada somente para essa fatura.
Uma forma de verificar se a cobrança automática está agendada, é consultar nos detalhes da fatura se são exibidas as informações de cobrança automática como abaixo:

## Modo de teste
Sua instância pode ser configurada para o modo de teste a fim de testar a integração e as credenciais da iugu. Para isso utilize as credenciais do ambiente de teste lembrando
de definir nas [configurações da instância](https://docs.lacunasoftware.com/articles/signer/on-premises/settings) a opção de teste também.
No modo de teste, apenas cartões de créditos de teste podem ser utilizados conforme definido na página [Usar cartões em modo teste](https://support.iugu.com/hc/pt-br/articles/212456346-Usar-cart%C3%B5es-de-teste-em-modo-de-teste).
Para testar o pagamento com Pix, basta utilizar o botão que aparece abaixo do QR code:

Será copiada uma URL com estrutura semelhante à exibida abaixo:
```javascript
http://faturas.iugu.com/iugu_pix/a32c46b6-ab85-469e-bafc-601c1a4e96ae/test/pay
```
Acesse essa URL no navegador para simular a realização do pagamento.
Para testar o pagamento com o boleto, obtenha a URL da mesma forma que no pix, mas troque `test` por `sample` e `iugu_pix` por `iugu_bank_slip` conforme abaixo:
```javascript
http://faturas.iugu.com/iugu_bank_slip/a32c46b6-ab85-469e-bafc-601c1a4e96ae/sample/pay
```
# [Sistema de Nota Fiscal de Serviços Eletrônica (NFS-e)](https://docs.lacunasoftware.com/articles/signer/on-premises/nfse)
Permite a emissão e cancelamento de NFS-e para faturas pagas diretamente no Signer com uma integração ao [NFE.io](https://nfe.io/)
## Integração do Signer com o NFE.io
Atualmente as principais capitais do Brasil já possuem integração, porém a lista completa de prefeituras já integradas precisa ser solicitada à equipe comercial do NFE.io, juntamente com os preços.
### Primeiros passos
1. Faça o credenciamento na prefeitura para emissão de NFS-e, utilize o [documento da NFE.io](https://nfe.io/docs/documentacao/nota-fiscal-servico-eletronica/credenciamento-prefeitura/).
2. Crie uma conta - https://nfe.io/docs/nossa-plataforma/criar-conta/
3. Crie uma empresa - https://nfe.io/docs/nossa-plataforma/criar-empresa/
4. Insira os dados fiscais - https://nfe.io/docs/nossa-plataforma/alterar-empresa/
5. Faça o upload do certificado digital - https://nfe.io/docs/nossa-plataforma/upload-certificado/
6. [Entre em contato com a NFE.io](https://nfe.io/contato/) para negociação de preços e ativação da conta para produção.
7. Ainda em contato com o NFE.io, consulte se a prefeitura utilizada para emitir a nota exige o CNAE (Classificação Nacional de Atividades Econômicas). Caso seja necessário utilize a [busca online CNAE](https://concla.ibge.gov.br/busca-online-cnae.html) do IBGE.
8. Caso existam dúvidas específicas sobre NFS-e, a NFE.io disponibiliza um [documento](https://nfe.io/docs/documentacao/nota-fiscal-servico-eletronica/conceitos/) que resume explicações sobre a Nota Fiscal de Serviço.
### Obtenção de credenciais
Para configurar a integração será preciso obter a chave de acesso e o id de sua empresa. Acesse o menu Empresas, depois clique no nome da empresa que deseja emitir a NFS-e.

Depois, deslize pela página até encontrar a seção `Chaves de Acesso`. Nela são exibidas: a chave de acesso (Api Key) e o ID de empresa (CompanyId).

### Criação do Webhook
O próximo passo é a criação do webhook, serviço responsável por notificar o Signer quando as NFS-e forem emitidas ou canceladas. Acesse a opção Conta, deslize a página
e selecione a seção `Webhooks`:

Depois clique no botão Criar Webhook:

Selecione o tipo de webhook como `NFS-e` e configure os campos conforme a imagem abaixo:

O campo endereço (URL) deve ser preenchido com o endereço de sua instância do Signer seguido do caminho padrão de webhooks conforme o exemplo abaixo:
```text
https://seu-signer.com.br/api/webhooks/nfeio/nfse/changed
```
Por fim, o campo senha para autenticação da mensagem (HMAC) pode ser uma senha qualquer, mas é recomendado que tenha pelo menos 8 dígitos.
### Código de serviço
O Código de Serviço é um número que define o tipo de serviço prestado para ser utilizado na NFS-e. Esse código é fornecido pela prefeitura na qual será emitida a nota,
assim como a alíquota de imposto municipal.
O NFE.io disponibiliza uma lista de serviços cadastrados para a cidade que será emitida a NFS-e. Acesse o menu Empresas, selecione a empresa que emitirá a NFS-e
e clique na seção `Lista de serviços cadastrados`.


Como mencionado anteriormente, cada prefeitura possui sua própria lista de códigos e sua descrição. Usando Brasília como exemplo, o Signer se enquadra no código ``0103`` , mas caso haja dúvidas sugerimos que consulte o contador de sua empresa.

## Emissão e cancelamento de NFS-e no Signer
### Emitir notas
Depois que todos os parâmetros tenham sido configurados com o NFE.io e o Signer, será possível emitir notas diretamente pela página de Detalhes da fatura, para faturas pagas.

> **Observação**
>
> * Para emitir ou cancelar notas é preciso ser o administrador da instância.
> * O sistema de notas fiscais não depende de integração com a iugu para funcionar.
> * Caso a instância do Signer também possua integração com a iugu, ao realizar o pagamento da fatura, automaticamente será feito o pedido de emissão da NFS-e.
> **Aviso**
>
> A emissão e o cancelamento de NFS-es depende do sistema da prefeitura. Se o sistema da prefeitura estiver instável, algumas notas podem demorar horas ou até dias
> para serem emitidas/canceladas.
Também é possível emitir uma NFS-e para uma fatura com a requisição abaixo:
```javascript
POST /api/invoices/{id}/receipts
```
Quando a nota fiscal é emitida, um e-mail do próprio NFE.io é enviado ao usuário/organização responsável pela fatura com o PDF e o XML da NFS-e. Além disso, é
possível baixar ou visualizar a nota na página de Detalhes da fatura:

### Cancelar notas
Depois que uma NFS-e é emitida, seu cancelamento pode ser feito na tela de Detalhes da fatura.

Também é possível cancelar uma NFS-e para uma fatura com a requisição abaixo:
```javascript
DELETE /api/invoices/{id}/receipts
```
> **Aviso**
>
> O cancelamento só pode ser feito depois que a nota é emitida, caso ainda esteja em processo de emissão deverá aguardar até que seja emitida. A mesma lógica se aplica
> para uma nova emissão realizada após um cancelamento.
# [Whatsapp](https://docs.lacunasoftware.com/articles/signer/on-premises/whatsapp)
Na versão 1.56.0 foi adicionada a possibilidade de integração para enviar notificações via Whatsapp.
Para isso, o sistema utiliza a [Whatsapp Business API](https://developers.facebook.com/docs/whatsapp) disponibilizada pela própria Meta.
Abaixo são listados os pré-requisitos desse tipo de integração:
* Ter uma [Meta Business Account](https://business.facebook.com/) verificada.
* Ter uma Whatsapp Business Account associada à Meta Business Account.
* Ter um número de telefone (celular ou fixo) verificado e associado à sua Whatsapp Business Account.
* O número de telefone [não pode ter sido cadastrado nos Apps de Whatsapp e Whatsapp Business](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started/migrate-existing-whatsapp-number-to-a-business-account).
## Configuração da conta de Whatsapp
* Acesse a página de [Configurações de Negócio](https://business.facebook.com/settings) da sua Meta Business Account.
* Selecione o item `Contas` e em seguida `Contas do Whatsapp`. Clique no botão `Adicionar` para cadastrar o número que será utilizado para enviar
mensagens de Whatsapp.
* Caso sua Business Account não esteja verificada ainda, inicie o procedimento de verificação (pode demorar cerca de dois dias úteis).
* Após verificar sua conta e o número que será utilizado, acesse o [Gerenciador do Whatsapp](https://business.facebook.com/wa/manage/home) da conta
de Whatsapp criada.
## Configuração de modelos de mensagens
Por exigência da Meta, todas mensagens iniciadas pela aplicação devem ser registradas previamente por meio de modelos. Caso contrário, só poderão ser
enviadas caso o usuário entre em contato previamente com seu número de whatsapp (iniciando assim uma [Conversa](https://developers.facebook.com/docs/whatsapp/conversation-types)).
Uma vez verificado seu número de Whatsapp, acesse a [Gestão de Modelos](https://business.facebook.com/wa/manage/message-templates/) para registrar os modelos
das mensagens que serão enviados pela aplicação.
Como a aplicação suporta 3 idiomas (Português, Inglês e Espanhol), cada modelo deve ter três versões: Portuguese (BR), English e Spanish.
Os modelos possuem parâmetros e, por isso, na criação, deve ser enviado um exemplo dos valores de cada um desses parâmetros. É importante que os valores sejam os mesmos
entre as versões de idiomas do mesmo modelo.
Além disso, quaisquer URLs que forem adicionadas como exemplos de parâmetros precisam ser funcionais e coerentes com os demais parâmetros.
Os modelos serão revisados pela equipe Meta. Esse processo demora em média dois dias úteis.
Entre em contato com nosso suporte para obter a lista de modelos que devem ser cadastrados, bem como o nome e conteúdo dos templates. Você terá liberdade para mudar o
conteúdo dos modelos desde que não altere a quantidade nem a ordem dos parâmetros.
## Configuração do Parceiro de tecnologia
Após configurada a conta e os modelos, é preciso definir a Lacuna Software como parceiro de tecnologia para que o Signer possa acessar a API da Meta.
1. Acesse o [painel de contas de Whatsapp](https://business.facebook.com/latest/settings/whatsapp_account) de sua Business Account.
1. Escolha a conta desejada e clique no botão "Associar parceiro (Assign partner)":

1. Na janela que se abre, informe o código de parceiro da Lacuna Software (solicite ao suporte) e marque a opção "Controle total (Full Control)":

1. Por fim, entre em contato com nossa equipe de suporte informando que a associação já foi realizada, para que possamos habilitar a integração.
Se você tem uma instância On-Premises, veja a seção de [Configurações de Whatsapp](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-whatsapp-v1560)
para saber como definir as configurações que habilitam a integração.
# [Envio de SMS](https://docs.lacunasoftware.com/articles/signer/on-premises/sms)
Quando o envio de SMS está habilitado (assinatura eletrônica com código via SMS), o Signer precisa de uma
conta em um dos **provedores de SMS suportados**. Os parâmetros ficam na seção **`SMS`** das configurações
da instância (veja [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings)).
## Configuração básica
Para habilitar, defina `Enabled` como `true`, escolha o provedor em `Type` e informe as credenciais dele:
```json
{
"SMS": {
"Enabled": true,
"Type": "Twilio",
"MessageFrom": "+12125550000",
"AccountSid": "AC00000000000000000000000000000000",
"AuthToken": "ffffffffffffffffffffffffffffffff"
}
}
```
Com **Total Voice**, por exemplo:
```json
{
"SMS": {
"Enabled": true,
"Type": "TotalVoice",
"AccessToken": "0123456789abcdef0123456789abcdef"
}
}
```
> **Observação**
>
> O `Type` diferencia maiúsculas de minúsculas e deve ser um dos valores da tabela abaixo. Cada provedor tem
> uma propriedade `Endpoint` opcional, usada apenas para apontar para um ambiente alternativo (homologação ou
> proxy); deixe-a de fora para usar o endpoint padrão do provedor.
## Provedores suportados
| Provedor | `Type` | Parâmetros |
|---|---|---|
| Twilio | `Twilio` | `AccountSid`, `AuthToken`, `MessageFrom` (ou `MessagingServiceSid`) |
| Total Voice | `TotalVoice` | `AccessToken` |
| Comtele | `Comtele` | `Sender`, `AuthToken` |
| Zenvia | `Zenvia` | `Account`, `Password` |
| Pontaltech | `Pontaltech` | `User`, `Password` |
| Pontaltech (rota rápida) | `FastPontaltech` | `User`, `Password` |
| SMS Empresa | `SmsEmpresa` | `ChaveKey` |
| SMS Token | `SmsToken` | `Key` |
| Tigo | `Tigo` | `ApiKey` |
| Eyou | `Eyou` | `Username`, `Password`, `ApiKey`, `CostCenter` |
| Amazon SNS | `AmazonSns` | `Region`, `AccessKeyId`, `SecretAccessKey` |
| Genérico (HTTP) | `Generic` | `Endpoint`, `AuthType` (`NoAuth`, `Basic`, `Bearer`, `ApiKey`) e as credenciais do modo escolhido |
| Simulador (testes) | `Simulator` | nenhum. Apenas registra o envio no log, sem enviar SMS de fato |
> **Dica: Twilio**
>
> Além das credenciais, o Twilio suporta a proteção contra fraude de *SMS pumping*. Prefira `AccountSid` +
> `AuthToken` em vez do antigo par de credenciais básicas.
## Vários provedores por país
É possível configurar mais de um provedor e roteá-los por prefixo internacional do número de destino,
usando `EnabledPrefixes` (lista separada por vírgula). Cada provedor fica em uma subseção nomeada:
```json
{
"SMS": {
"Enabled": true,
"Brasil": {
"Type": "TotalVoice",
"AccessToken": "...",
"EnabledPrefixes": "+55"
},
"Internacional": {
"Type": "Twilio",
"AccountSid": "...",
"AuthToken": "...",
"MessageFrom": "+12125550000",
"EnabledPrefixes": "+1,+54"
}
}
}
```
## Veja também
* [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings)
* [Integração com WhatsApp](https://docs.lacunasoftware.com/articles/signer/on-premises/whatsapp): autenticação via WhatsApp em vez de SMS
# [Servidor de e-mail (SMTP)](https://docs.lacunasoftware.com/articles/signer/on-premises/email-smtp)
O Signer envia e-mails de notificação (documentos pendentes, conclusão, códigos de autenticação etc.) por um
provedor configurado na seção **`Email`** das configurações da instância (veja
[Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings)). O provedor padrão é o **envio por SMTP**, mas também há suporte
a Amazon SES e SendGrid.
## Envio por SMTP (padrão)
Para enviar pelo **seu próprio servidor SMTP**, defina `Enabled` como `true` e informe o servidor e as
credenciais. Não é necessário informar `Type`: quando ele não é informado, o Signer usa automaticamente o
**MailKit**, que é o provedor SMTP padrão da instância.
```json
{
"Email": {
"Enabled": true,
"ServerHost": "smtp.suaempresa.com",
"Username": "no-reply@suaempresa.com",
"Password": "sua-senha",
"SenderAddress": "no-reply@suaempresa.com",
"SenderName": "Sua Empresa"
}
}
```
Parâmetros disponíveis:
| Parâmetro | Descrição |
|---|---|
| `ServerHost` | Endereço (host) do servidor SMTP |
| `Username` / `Password` | Credenciais de autenticação no servidor SMTP |
| `SenderAddress` | Endereço de e-mail usado como remetente |
| `SenderName` | Nome exibido como remetente (recomenda-se o mesmo nome da aplicação) |
| `ServerPort` | Porta do servidor SMTP, quando diferente do padrão (`587`) |
| `EnableSsl` | Define se a comunicação usa SSL. Por padrão, é habilitado |
| `ReplyToAddress` | Endereço opcional de resposta (*reply-to*) |
### Opções avançadas (MailKit)
O envio por SMTP usa o **MailKit**, que aceita alguns parâmetros adicionais para casos específicos:
| Parâmetro | Descrição |
|---|---|
| `SecureSocketOption` | Controla o modo de conexão segura: `None`, `Auto`, `SslOnConnect`, `StartTls` ou `StartTlsWhenAvailable`. Quando informado, tem precedência sobre `EnableSsl` |
| `PoolConnections` | Reutiliza conexões SMTP em um *pool* em vez de abrir uma nova a cada envio. Útil em volumes altos |
| `ConnectionKeepAliveSeconds` | Tempo (em segundos) que uma conexão do *pool* é mantida viva. Padrão: `15` |
| `ServerCertificateThumbprint` | *Thumbprint* do certificado esperado do servidor, para fixá-lo (*pinning*) |
| `DangerousAcceptAnyServerCertificate` | Aceita **qualquer** certificado do servidor. Use apenas em ambientes de teste, pois desabilita a validação TLS |
```json
{
"Email": {
"Enabled": true,
"ServerHost": "smtp.suaempresa.com",
"ServerPort": 587,
"SecureSocketOption": "StartTls",
"PoolConnections": true,
"ConnectionKeepAliveSeconds": 30,
"Username": "no-reply@suaempresa.com",
"Password": "sua-senha",
"SenderAddress": "no-reply@suaempresa.com",
"SenderName": "Sua Empresa"
}
}
```
> **Cuidado**
>
> `DangerousAcceptAnyServerCertificate` desativa a verificação do certificado TLS e expõe a conexão a
> ataques *man-in-the-middle*. Em produção, prefira `ServerCertificateThumbprint` se precisar confiar em um
> certificado específico (por exemplo, autoassinado).
### Provedor legado (Legacy)
Instâncias configuradas antes da introdução do MailKit podem ter `Type` definido explicitamente como
`Legacy`. Esse provedor usa a implementação SMTP nativa do .NET e aceita apenas os parâmetros básicos
(`ServerHost`, `ServerPort`, `Username`, `Password`, `SenderAddress`, `EnableSsl`). Nenhuma das opções
avançadas acima está disponível para ele.
> **Dica**
>
> Não há necessidade de migrar uma instância que já funciona com `Legacy`. Para uma configuração nova,
> prefira deixar `Type` em branco (ou defini-lo como `MailKit`) para aproveitar o pool de conexões e as
> demais opções avançadas.
## Amazon SES
Para enviar pelo Amazon SES, defina `Type` como `AwsSes`:
```json
{
"Email": {
"Enabled": true,
"Type": "AwsSes",
"Region": "us-east-1",
"AccessKey": "...",
"SecretKey": "...",
"SenderAddress": "no-reply@suaempresa.com",
"SenderName": "Sua Empresa"
}
}
```
> **Dica: Credenciais na AWS**
>
> Em uma instância rodando na AWS, você pode dispensar `AccessKey`/`SecretKey` e usar as credenciais do próprio
> ambiente: informe `InstanceProfileRole` (perfil de instância) ou defina `AssumeRoleWithWebIdentity` como
> `true`.
## SendGrid
Para enviar pelo SendGrid, defina `Type` como `SendGrid`:
```json
{
"Email": {
"Enabled": true,
"Type": "SendGrid",
"ApiKey": "...",
"SenderAddress": "no-reply@suaempresa.com",
"SenderName": "Sua Empresa"
}
}
```
> **Dica**
>
> Para personalizar a **aparência** e os **textos** dos e-mails (logo, cores, notificações), veja
> [E-mails e notificações](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/emails). Esta página trata apenas do **canal de envio**.
## Veja também
* [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings)
* [E-mails e notificações](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/emails)
# [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings)
### Configurações *General*
Na seção **General**:
* **SiteUrl**: URL publicamente acessível do site (ex.: `https://signer.patorum.com/`). Este endereço é usado para compor e-mails com links de volta para o site.
* **EncryptionKey**: chave de criptografia.
* **EncryptionCertThumb**: thumbprint do certificado da chave de criptografia.
* **RootPasswordHash**: hash da senha de root, calculado acima.
* **AutoUpdateDatabase**: por padrão, a aplicação tenta fazer as alterações de modelo no banco de dados após uma atualização (quando necessário). Defina como `false` se a aplicação não tiver permissões de proprietário sobre o banco de dados.
* **SupportEmailAddress**: o endereço de e-mail de suporte (usado no rodapé dos e-mails enviados).
* **PersonalAccountsEnabled** (obsoleta desde a v1.63.0): se `true`, qualquer usuário que entre ou se cadastre no site pode usar a aplicação e criar documentos na sua conta pessoal. Se `false`, os usuários só
poderão usar a área logada se tiverem sido adicionados previamente a uma organização. Use as [configurações de Access Control](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-access-control-v1630), pois esta opção está obsoleta.
* **AccessRequestEmailAddress** (v1.38.0): o endereço de e-mail para o qual as solicitações de acesso são enviadas (se as contas pessoais não estiverem habilitadas). Se definido, quando um usuário vê a tela
"Sem acesso", aparece um botão que permite a ele solicitar acesso ao sistema. O endereço de e-mail configurado recebe o nome e o e-mail do usuário que solicitou
o acesso.
* **AllowCreateDocumentsByDefault** (padrão: `true`, v1.12.0): se `true`, os usuários poderão criar documentos após se cadastrarem. Se `false`, o papel de criador de documentos deve ser concedido
individualmente a cada usuário pelo administrador do sistema.
* **AllowCreateOrganizationsByDefault** (padrão: `true`, v1.12.0): se `true`, os usuários poderão criar organizações após se cadastrarem (se as contas pessoais estiverem habilitadas).
* **EnableDocumentTypes** (obsoleta desde a v1.7.0): se `true`, exibe uma seleção para definir o tipo de um documento ao criá-lo. Os tipos disponíveis são `Deed` e `Power of Attorney`.
Use as [configurações de DocumentTypes](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-documenttypes-v170), pois esta opção está obsoleta.
* **OriginalFileWatermarkText**: documentos do tipo `Deed` terão uma marca d'água com este texto na versão original até que o documento seja concluído.
* **EnableElectronicSignature** (obsoleta desde a v1.1.0): se `true`, exibe a opção de aceitar assinaturas eletrônicas ao criar documentos. Use as [configurações de ElectronicSignature](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-electronicsignature),
pois esta opção está obsoleta.
* **NotifyConclusion** (v1.5.0): se `true`, envia um e-mail a todos os participantes de um documento quando ele é concluído.
* **NotifyParticipantsOnAddedVersion** (v1.66.0): se `true`, envia um e-mail a todos os participantes ativos de um documento quando uma nova versão do documento é adicionada. Se não estiver habilitada, somente os observadores
serão notificados.
* **NotifyParticipantsOnAttachmentUpdates** (v1.66.0): se `true`, envia um e-mail a todos os participantes ativos de um documento quando anexos são adicionados ou excluídos. Se não estiver habilitada, somente os observadores
serão notificados.
* **NotifyParticipantsOnDocumentInformationUpdates** (v1.66.0): se `true`, envia um e-mail a todos os participantes ativos de um documento quando as informações do documento são atualizadas. Se não estiver habilitada, somente os observadores
serão notificados.
* **AreActionsOrderedByDefault** (v1.6.0): se `true`, as ações do fluxo do documento serão ordenadas por padrão, a menos que o usuário altere isso durante a criação do documento.
* **FlowActionTicketValidityMinutes**: define a validade, em minutos, do ticket enviado no e-mail de lembrete de assinatura, que permite ao usuário assinar sem entrar no sistema.
O valor padrão é `1440` (24 horas).
* **RestrictedUserAutocomplete** (padrão: `true`): se `true`, as sugestões de preenchimento automático de participantes mostram apenas os usuários que estão nas mesmas organizações que o usuário atual e (a partir da v1.8.0)
os que foram adicionados como participantes nos documentos do usuário atual.
Se `false`, as sugestões buscam entre todos os usuários do banco de dados. Esta opção não afeta o preenchimento automático para os administradores do sistema.
* **EnableDocumentAuthentication**: se `true`, exibe a opção de autenticação de documento na tela de assinatura rápida. Isso permite que o usuário autentique um documento inserindo
uma representação visual personalizada com informações do seu registro de advogado ou de contador.
* **EnableBilling** (obsoleta desde a v1.40.0): se `true`, habilita o módulo de cobrança. Use a opção nas [configurações de Billing](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-billing-v1400), pois esta opção está obsoleta.
* **EnableBillingInformationCheck** (v1.19.0, obsoleta desde a v1.40.0): se `true`, habilita a verificação de que as informações de cobrança foram preenchidas sempre que uma transação paga é criada por um usuário
ou por uma organização. Use a opção nas [configurações de Billing](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-billing-v1400), pois esta opção está obsoleta.
* **UseCustomStaticHomePage**: se `true`, usa o arquivo estático index.html do theme-assets como página inicial. Após o login, os usuários sempre serão redirecionados para
`/private`, já que a página inicial será estática. Você pode alterar a URL de redirecionamento com a configuração `RedirectUri` na seção de configuração do OIDC.
* **HidePoweredBy** (v1.9.0): se `true`, oculta a seção "powered by" exibida na página inicial padrão.
* **EnableSignaturePositioning** (padrão: `true`): se `true`, exibe a opção de pré-selecionar a posição da assinatura de um signatário ao criar documentos.
* **EnableSignatureMarksDownload**: se `true`, permite que o usuário baixe a versão com marcas de assinatura de um documento.
* **EnableSigningTagsDownload** (padrão: `true`, v1.5.0): se `true`, permite que o usuário baixe as signing tags de um documento.
* **EnablePrinterFriendlyDownload** (padrão: `true`, v1.5.0): se `true`, permite que o usuário baixe a versão para impressão de um documento.
* **DocumentConcludedNotificationAttachmentType** (padrão: `PrinterFriendlyVersion`, v1.35.0): define a versão do documento enviada como anexo quando o
documento é concluído. Os valores disponíveis são: `Original`, `PrinterFriendlyVersion`, `Signatures`, `OriginalWithMarks`, `SigningTags` e `SignatureMarks`.
* **SendOriginalFileWithPendingActionEmail** (v1.36.0): se `true`, envia a versão original do documento como anexo nas notificações de ação pendente.
* **SendPublicAttachmentFilesWithPendingActionEmail** (v1.75.0): se `true` e `SendOriginalFileWithPendingActionEmail` também for `true`, adiciona os anexos do documento às notificações de ação pendente.
* **SendPublicAttachmentFilesWithDocumentConcludedEmail** (v1.75.0): se `true`, adiciona os anexos do documento à notificação de documento concluído.
* **EmailAttachmentSizeLimit**: o tamanho máximo de arquivo (em bytes) que pode ser anexado a um e-mail. O valor padrão é `10485760` (10 MB).
* **UserManualUrl**: se definida, exibe (ao lado do menu do usuário) um botão para um PDF com o manual do usuário da aplicação.
* **SetPasswordEmailExpiration**: a validade do link de definição de senha, em minutos. O valor padrão é `1440` (24 horas).
* **IdentifierType** (padrão: `Cpf`, v1.7.0, obsoleta desde a v2.11.0): o tipo de identificador de usuário desta instância. As opções disponíveis são `Cpf` e `EcuadorBceId`. Use as [configurações de IdentifierScope](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-identifierscope-v2110), pois esta opção está obsoleta.
* **FilterCertificatesByIdentifier** (padrão: `true`, v1.7.0): se `true`, filtra automaticamente os certificados pelo identificador do usuário.
* **FilterCertificatesByKeyUsage** (v1.7.0): se `true`, filtra os certificados que têm os atributos de não repúdio e de assinatura digital.
* **MaximumItemsInBatchNotifications** (padrão: `10`, v1.10.4): define o número máximo de itens incluídos nas notificações que listam um
lote de itens (por exemplo, todos os documentos em que um usuário foi adicionado como observador).
* **UngroupedNotificationsDelayInSeconds** (padrão: `180`, v1.56.0): quando várias notificações precisam ser enviadas a um usuário sobre um ou mais
documentos (se o tipo de destinatário não for e-mail), cada notificação é enviada com um intervalo crescente, em segundos, definido por esta configuração.
* **NormalizePdfs** (padrão: `true`, v1.13.0): se `true`, o sistema tenta normalizar automaticamente os PDFs enviados para criar um documento, para
evitar erros de arquivo protegido e de modo de anexação (append mode).
* **MaxUploadSizeInBytes** (padrão: `106954752` - 100 MB, v1.15.0): define o tamanho máximo de upload, em bytes. Há um limite superior de 1 GB, que só pode ser alterado
atualizando o arquivo web.config no pacote de instalação.
* **EnableDeletedDocumentValidation** (v1.18.0): se `true`, o sistema valida documentos pelo código de validação mesmo que eles tenham sido excluídos
pelos seus proprietários.
* **AllowSetFlowActionEmail** (padrão: `true`, v1.20.0): se `true`, permite que um participante tenha um e-mail específico para o documento, ou seja, se um documento for enviado a um usuário
mas o e-mail informado for diferente do cadastrado para esse usuário, o e-mail informado será o considerado nas notificações do(s) documento(s) enviado(s).
Esta opção não tem efeito se o nome de usuário for o e-mail.
* **AllowSetFlowActionPhoneNumber** (padrão: `true`, v1.28.0): se `true`, permite que um participante tenha um número de telefone específico para o documento, ou seja, se um documento for
enviado a um usuário mas o telefone informado for diferente do cadastrado para esse usuário, o telefone informado será o considerado nas notificações
do(s) documento(s) enviado(s).
* **CountryIdentifierMode** (padrão: `Unique`, v1.21.0, obsoleta desde a v2.11.0): define o comportamento do identificador do país. Os tipos disponíveis são `Unique`, `NonUnique`, `Nullable` e
`NullableUnique`. Use as [configurações de IdentifierScope](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-identifierscope-v2110), pois esta opção está obsoleta.
* **SearchByIdentifierOnlyForUniqueIdentifierMode** (v1.64.0): a partir da v1.64.0, ao buscar um novo usuário, verifica primeiro o identificador do país se o modo do identificador for `Unique` ou
`NullableUnique`. Nas versões anteriores, isso só era feito para o modo `Unique` (defina como `true` para voltar a esse comportamento).
* **FilterCertificatesByEmailIfNoIdentifier** (padrão: `true`, v1.21.0): se `true`, filtra pelo e-mail os certificados digitais de um usuário que não tem identificador.
* **UpdatedUserWithoutIdentifierAfterSignature** (padrão: `true`, v1.21.0): se `true`, tenta adicionar um identificador a um usuário depois que ele assina um documento com sucesso
com um certificado que tem identificador.
* **DefaultCurrency** (padrão: `R$`, v1.22.0): define a moeda padrão da aplicação.
* **DocumentAcceptedMimeTypes** (v1.22.0): define os mime types aceitos no upload de documentos. Os valores devem ser separados por vírgula. Defina como null se os tipos não devem
ser restringidos.
* **HideMultipleDocumentNotificationViewDocumentsButton** (v1.27.0): se `true`, o botão **Ver documentos** não é incluído nos e-mails de ação pendente de vários
documentos. Isso garante que o e-mail contenha apenas links que não exigem que o usuário entre no sistema.
* **HideMultipleConcludedDocumentNotificationViewDocumentsButton** (v1.45.0): se `true`, o botão **Ver documentos** não é incluído no e-mail de documentos concluídos de vários
documentos. Isso garante que o e-mail contenha apenas links que não exigem que o usuário entre no sistema.
* **AllowAlgorithmNotAllowed** (v1.31.0): se `true`, aceita documentos assinados com algoritmos considerados inseguros, como o SHA-1.
* **MaxNumberSignaturesToValidateAtOnce** (padrão: `40`, v1.31.0): define o número máximo de assinaturas que um documento pode ter para que todas sejam validadas de uma vez, em vez de
em rodadas.
* **NumberSignaturesPerValidationRound** (padrão: `10`, v1.31.0): define o número de assinaturas validadas em cada rodada de validação, se o documento ultrapassar o número
especificado na configuração MaxNumberSignaturesToValidateAtOnce.
* **MaxTotalOfSignaturesForRoundsValidation** (padrão: `40`, v1.43.0): define o número máximo de assinaturas retornadas ao fazer a validação em rodadas.
* **MaxEllapsedSecondsForRoundsValidation** (padrão: `30`, v1.43.0): define o número máximo de segundos que podem ser usados em todas as rodadas de validação. Se esse tempo for ultrapassado,
a aplicação retorna todas as assinaturas validadas até o momento.
* **CanceledDocumentWatermarkText** (v1.33.0): o texto da marca d'água adicionada aos documentos cancelados.
* **ExpiredDocumentWatermarkText** (v1.33.0): o texto da marca d'água adicionada aos documentos expirados.
* **EnableEmailTemplateCustomization** (v1.33.0): se `true`, permite substituir os templates de e-mail padrão. Os arquivos devem ser HTML e seguir o padrão `Email-.html`.
* **HidePublicAreaMenuLinks** (v1.34.0): se `true`, oculta os links de menu da área pública, como os links de validação de documento e de login.
* **MaskUserIdentifier** (v1.35.0): se `true`, mascara os identificadores de usuário nas representações visuais de assinatura, no manifesto de assinaturas, na área pública e na maioria das telas que permitem aos usuários
ver informações de outros usuários.
* **MaskUserEmailAddress** (v1.47.0): se `true`, mascara os endereços de e-mail dos usuários no manifesto de assinaturas, na área pública e na maioria das telas que permitem aos usuários
ver informações de outros usuários.
* **ReplyToDocumentCreator** (v1.41.0): se `true`, adiciona o endereço de e-mail do criador do documento como endereço de resposta (reply-to) em todos os e-mails associados a esse documento.
* **FilterDocumentsByPendingForMe** (v1.43.0): se `true`, o filtro "Mostrar documentos pendentes para mim" fica habilitado por padrão na página de documentos.
* **MaxItemsRetrievedInOrganizationFilter** (padrão: `10`, v1.43.0): define o número máximo de organizações exibidas no filtro de organização da página de documentos (e de outras páginas).
* **MaxItemsRetrievedInFolderFilter** (padrão: `20`, v1.43.0): define o número máximo de pastas exibidas no filtro de pastas da página de documentos (e de outras páginas).
* **FlowActionTitleDisplayedByDefault** (v1.46.0): se `true`, exibe por padrão a caixa de título do participante.
* **UseOrganizationNameAsNotificationsSenderName** (v1.47.0): se `true`, as notificações sobre documentos são enviadas com o nome da organização como nome do remetente.
* **ParticipantWelcomeMessageIntervalInMinutes** (padrão: `0`, v1.47.0): se positivo, define quantos minutos aguardar para enviar a notificação de boas-vindas ao participante
depois que ele assinar um documento pela primeira vez (e ainda não tiver definido sua senha). Qualquer valor negativo desabilita esta notificação.
* **OrganizationUserWelcomeMessageDisabled** (v1.58.3): se `true`, não envia a notificação de boas-vindas depois que um usuário é adicionado a uma organização.
* **HideParticipantsListAndOrderOfActionsForNonManagers** (v1.50.0): oculta as informações dos participantes na página de detalhes do documento para todos os usuários, exceto os gestores ou administradores da organização do documento.
* **DisablePendingFlowActionsApi** (v1.50.0): desabilita a API que retorna as informações dos participantes pendentes dos documentos. Com isso, os participantes pendentes
não são exibidos na página de documentos.
* **DocumentExpirationTime** (v1.54.0): define o horário de expiração dos documentos no formato timespan (`HH:mm:ss`). Por padrão (se não definido), considera-se como horário de expiração o
fim do dia no fuso horário padrão configurado. Esta opção só deve ser usada se o fuso horário padrão for o Horário Padrão de Brasília (GMT-3).
* **ShortSiteName** (v1.56.0): um nome curto para se referir à aplicação em mensagens com limite pequeno de caracteres (como mensagens SMS). Deve ser uma versão mais curta
da configuração `SiteName`.
* **UpdateSubscriptionAccessTimeoutDelayInSeconds** (v1.60.0, padrão: `60`): tempo de espera para reagendar os jobs de atualização de acesso da subscription se já houver um job do mesmo tipo em execução.
Se definido como `0`, desabilita esse comportamento.
* **DisableOrganizationRenameByNonInstanceAdmin** (v1.63.0): se `true`, os administradores de organização não poderão atualizar as informações da organização, como nome e identificador.
Somente os administradores da instância poderão fazer essas alterações.
* **EnableSignatureValidationWhenGeneratingManifest** (v1.63.0): se `true`, sempre que um manifesto de assinaturas for gerado, as assinaturas do documento assinado associado serão
validadas. Fica desabilitada por padrão por causa do impacto no desempenho.
* **EnableSignaturesManifestDownloadInDocumentValidation** (v1.67.0): se `true`, uma opção de download do manifesto de assinaturas é exibida na validação de documentos.
* **AllowUserInfoUpdateBySubscription** (v.1.64.0): se `true`, os usuários criados ao criar um documento ou ao adicionar um usuário a uma subscription poderão ter suas informações
atualizadas pelos administradores dessa subscription.
* **RequireFolderSelectionWhenCreatingDocuments** (v.1.65.0): se `true`, os usuários são obrigados a selecionar uma pasta ao criar documentos.
* **AllowBasicAndOperatorUsersToManageContacts** (padrão: `false`, v2.17.1): se `true`, os usuários com o papel Basic ou Operator em uma organização também podem criar, atualizar e excluir os contatos da organização. Por padrão, somente Admin e Manager podem gerenciar contatos.
* **MaxWebhooksPerOrganization** (v1.65.0, padrão: `5`): número máximo de webhooks que podem ser adicionados a uma organização.
* **DocumentConcludedWebhookDelayInSeconds** (v1.66.0, padrão: `60`): tempo de espera, em segundos, até que o webhook de documento concluído seja disparado.
* **EnableCustomizableTranslationFiles** (v1.69.0): se `true`, habilita a personalização dos arquivos de tradução do frontend.
* **EnableCustomizableLocalizationResourceFiles** (v1.69.0): se `true`, habilita a personalização dos arquivos de recursos de localização do backend.
* **AllowFlowActionRefusalToNotRefuseDocument** (v1.70.0): se `true`, quando uma ação é recusada, o status do documento não é alterado para recusado (desde que ainda possa haver
pelo menos uma ação concluída com sucesso).
* **MarksSessionExpirationInMinutes** (v1.70.0, padrão: `60`): a validade padrão, em minutos, de uma sessão de posicionamento de marcas.
* **MaxSignersPerDocument** (v1.71.0, padrão: `30`): o número máximo de signatários permitido por documento.
* **HideDownloadOptionForPendingDocuments** (v1.76.0): se `true`, não exibe a opção de download para documentos pendentes. Esta configuração pode ser sobrescrita pelas configurações da organização.
* **EnableInitialStepForActionInPublicArea** (v1.79.0): a partir da v1.79.0, as páginas públicas de assinatura e de aprovação exibem o documento diretamente ao usuário, sem a
página inicial com os detalhes do documento. Se `true`, volta ao comportamento das versões anteriores.
* **DisableSwagger** (v1.80.0): se `true`, desabilita o Swagger.
* **DisableLTV** (v2.2.0): se `true`, desabilita o LTV nas assinaturas PAdES.
* **DisableRefuseSignature** (v2.2.0): se `true`, desabilita a opção de recusar assinaturas na página de assinatura.
* **MaxItemsRetrievedInBillingPlanFilter** (v2.3.0, padrão: `10`): define o número máximo de itens retornados no filtro de planos de cobrança.
* **RemoveJsFromPdfs** (v2.4.6, padrão: `true`): habilita a remoção automática de objetos Javascript dos PDFs, para aumentar a segurança.
* **EnableDocumentValidationInThePublicArea** (v2.4.0, padrão: `true`): habilita a página e os endpoints de validação de documentos.
* **DisableNotifyParticipantsOnDocumentDeleted** (v2.6.0, padrão: `false`): impede que notificações de documento excluído sejam geradas na instância.
* **PublicUploadEnabled** (v2.6.0, padrão: `true`): exige que os usuários estejam autenticados para fazer uploads na plataforma.
* **PublicBatchSignatureEnabled** (v2.7.0, padrão: `false`): se `true`, habilita a opção de assinatura em lote na área pública. Isso permite que os usuários assinem vários documentos de uma vez sem precisar entrar no sistema, usando um ticket enviado no e-mail de lembrete de assinatura.
* **IncludeQrCodeInEmailNotifications** (v2.9.0, padrão: `false`): se `true`, inclui nas notificações por e-mail um QR code que leva ao documento na plataforma por meio do aplicativo WebPKI.
* **CleanupFailedHangfireJobsCommandTimeoutSeconds** (padrão: `300`, v2.21.0): timeout de comando, em segundos, de cada exclusão em lote feita pelo job de limpeza dos jobs do Hangfire que falharam. Essa limpeza é executada sobre o schema do Hangfire em uso e pode ter de aguardar brevemente por locks, por isso precisa de um prazo maior que o timeout padrão de requisição. Defina como zero ou menos para voltar ao timeout padrão.
### Configurações *Billing* (v1.40.0)
Na seção **Billing**:
* **Enabled**: se `true`, habilita o módulo de cobrança.
* **EnableBillingInformationCheck**: se `true`, habilita a verificação de que as informações de cobrança foram preenchidas sempre que uma transação paga é criada por um usuário
ou por uma organização.
* **TrialPeriodDays**: se maior que `0`, habilita o período de avaliação de cobrança e define quantos dias ele dura. Durante o período de avaliação, todas as transações de uma
organização são gratuitas (a data de criação da organização é considerada a data de início).
* **EnableBillingPlanHiring** (v1.53.0): se `true`, os usuários poderão escolher e contratar planos de cobrança. Se habilitada, é preciso configurar um gateway de pagamento.
* **MaxNumberOfAvailablePlansDisplayed** (padrão: `20`, v1.53.0): o número máximo de planos de cobrança exibidos na página de contratação de planos.
* **AllowBasicUsersToViewOrganizationBillingInformation** (v1.62.0): se `true`, os usuários básicos poderão ver as faturas e as informações de cobrança de uma organização.
* **BlockSubscriptionDeletionIfHasPaymentDebt** (padrão: `true`, v1.78.0): se `true`, os administradores de organização não podem excluir uma subscription que tenha débitos de pagamento. O
administrador da instância, no entanto, poderá excluí-la se necessário.
* **EnableInformationCheckForZeroValueInvoices** (padrão: `false`, v2.22.0): quando `EnableBillingInformationCheck` também está habilitada, estende a exigência das informações de cobrança às faturas que ainda não acumularam valor cobrável (subscriptions em período de avaliação e subscriptions pré-pagas ainda sem cobrança lançada na fatura), em vez de aplicá-la somente quando o valor da fatura for maior que zero.
### Configurações *DocumentTypes* (v1.7.0)
Na seção **DocumentTypes**:
* **Enabled**: se `true`, exibe uma seleção para definir o tipo de um documento ao criá-lo. Os tipos disponíveis são `Deed` (obsoleto desde a v1.33.0), `Power of Attorney` (obsoleto desde a v1.33.0) e `Prescription`.
* **EnabledOptions**: lista, separada por vírgulas, dos tipos que serão habilitados/exibidos. A ordem dos tipos é respeitada na exibição. Exemplo: `PowerOfAttorney,Deed,Prescription`.
Se esta configuração não for informada e os tipos de documento estiverem habilitados, todas as opções disponíveis serão exibidas.
### Configurações *SupportChat* (v1.6.0)
Na seção **SupportChat**:
* **Type**: o provedor do chat de suporte. As opções disponíveis são `Movidesk`, `Jivo` ou `Custom` (v1.44.0).
* **Enabled**: se `true`, habilita o chat de suporte do tipo selecionado.
* **Key**: a chave da conta do chat de suporte.
* **ScriptUrl** (v1.44.0): se o tipo for `Custom`, define a URL de onde o script do chat é carregado.
### Configurações *ElectronicSignature*
Na seção **ElectronicSignature**:
* **Enabled**: se `true`, exibe a opção de aceitar assinaturas eletrônicas ao criar documentos.
* **EnableSmsAuthentication**: se `true`, habilita a opção de verificação por SMS nas assinaturas eletrônicas. Para funcionar corretamente, é preciso configurar um provedor de SMS (veja [SMS](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-sms)).
* **EnableWhatsappAuthentication** (v1.56.0): se `true`, habilita a opção de verificação por Whatsapp nas assinaturas eletrônicas. Para funcionar corretamente, as [configurações de Whatsapp](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-whatsapp-v1560) devem estar definidas.
* **EnableOtpAuthentication**: se `true`, habilita a opção de verificação por aplicativo de OTP nas assinaturas eletrônicas.
* **EnableSelfieAuthentication** (v1.23.0): se `true`, habilita a captura de selfie nas assinaturas eletrônicas.
* **EnableDatavalidAuthentication** (v1.32.0): se `true`, habilita a captura de selfie com validação pelo Datavalid nas assinaturas eletrônicas. Se habilitada, a configuração da seção
`Datavalid` também deve ser definida.
* **EnablePixAuthentication** (v1.32.0): se `true`, habilita a autenticação por Pix nas assinaturas eletrônicas. Se habilitada, a configuração da seção
`Gerencianet` também deve ser definida.
* **EnableIdScanAuthentication** (v1.77.0): se `true`, habilita a autenticação por leitura de documento de identidade (ID scan) nas assinaturas eletrônicas. Se habilitada, a configuração da seção
`RestPki` também deve ser definida.
* **EnableElectronicSignatureOfTermsOfUse** (padrão: `true`): se `true`, permite que os termos de uso sejam assinados eletronicamente.
* **IsAllowedByDefault**: se `true`, a opção de permitir assinaturas eletrônicas vem selecionada por padrão.
* **IsRequired** (v1.46.0): se `true`, o signatário **não** terá a opção de assinar com certificado digital.
* **IsGeolocationRequired** (v1.5.0): se `true`, a geolocalização do usuário precisa ser enviada para que ele possa assinar eletronicamente.
* **Mode** (padrão: `Timestamp` se houver um servidor de carimbo de tempo configurado, `SelfSigned` caso contrário, v1.8.0): o modo em que as assinaturas eletrônicas são feitas. Os modos disponíveis são `SelfSigned` e `Timestamp`:
* `SelfSigned`: um certificado autoassinado é gerado uma vez e usado em todas as assinaturas eletrônicas da aplicação.
* `Timestamp`: a assinatura eletrônica é adicionada ao documento como uma assinatura de carimbo de tempo. Requer que um servidor de carimbo de tempo (Timestamper) esteja configurado.
* **AdditionalInfoUrl** (v1.9.0): URL para a qual o usuário é redirecionado ao clicar no ícone de informações de assinatura eletrônica na tela de criação de documento.
* **UseSignatureCertificateAsSelfSignedCertificate** (v1.52.0): se `true` e um [certificado de assinatura](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-signature-certificate-v1520) estiver habilitado, ele é usado no lugar do certificado autoassinado gerado automaticamente.
* **EnableLivenessAuthentication** (v1.75.0 - *somente para testes internos*): se `true`, habilita a validação de prova de vida (liveness) nas autenticações por selfie e por Datavalid.
* **RequireLivenessOnSelfieAuthentication** (v1.75.0): se `true`, exige que as autenticações por selfie tenham validação de prova de vida.
* **RequireLivenessOnDatavalidAuthentication** (v1.75.0): se `true`, exige que as autenticações por Datavalid tenham validação de prova de vida.
* **HideEvidencesInPublicValidation** (v1.75.1): se `true`, oculta as evidências do signatário eletrônico na página pública de validação de documentos.
* **HideSubjectNameInXadesSignatures** (v1.76.0): se `true`, não adiciona o nome do titular (subject name) nas assinaturas XAdES.
* **DatabaseCommandTimeout** (v1.76.0): define o timeout de comandos do banco de dados, em segundos.
* **MaxLivenessSessionsPerSignature** (padrão: `5`, v2.13.0): número máximo de sessões de prova de vida que podem ser criadas por tentativa de assinatura. Use `-1` para ilimitado.
* **MaxIdScanSessionsPerSignature** (padrão: `5`, v2.13.0): número máximo de sessões de IdScan que podem ser criadas por tentativa de assinatura. Use `-1` para ilimitado.
### Configurações *CloudSignatureSession* (v2.19.0)
Na seção **CloudSignatureSession**:
* **Enabled** (padrão: `false`): se `true`, habilita a reutilização de sessões de assinatura em nuvem. Com ela habilitada, os usuários podem autorizar uma única autenticação para assinar vários documentos dentro de um período configurado, sem precisar informar o OTP repetidamente.
* **OrganizationAllowsSessionsByDefault** (padrão: `true`): se `true`, a reutilização de sessões de assinatura em nuvem vem habilitada por padrão nas novas organizações. Cada organização pode alterar esta configuração individualmente.
* **DefaultSessionExpirationMinutes** (padrão: `720`): a duração padrão da sessão, em minutos. O valor máximo permitido é `10080` (7 dias). Qualquer valor configurado acima desse limite é reduzido ao máximo.
* **CheckboxCheckedByDefault** (padrão: `true`): se `true`, a caixa de seleção "manter sessão ativa" vem marcada por padrão quando um usuário faz uma assinatura em nuvem, o que torna a reutilização de sessão opcional por exclusão (opt-out) em vez de por adesão (opt-in).
### Configurações *Signature Certificate* (v1.52.0)
Na seção **SignatureCertificate**:
* **BlobName**: o nome do certificado PFX armazenado no Blob Storage configurado.
* **BlobFolder** (padrão: `certificates`): a pasta onde o certificado fica armazenado no Blob Storage configurado.
* **Password**: a senha do certificado PFX. Recomenda-se que o certificado tenha senha.
### Configurações *TrustServices* (certificados em nuvem, v1.11.0)
Na seção **TrustServices**:
* **Enabled**: se `true`, exibe os provedores de certificados em nuvem sempre que uma assinatura for necessária. Só são exibidos os provedores que têm um certificado para o identificador
do usuário. Se o usuário não tiver nenhum certificado nos provedores configurados, nada é exibido.
* Opções de informações dos serviços (`Services`): cada serviço deve ser configurado individualmente, como mostrado abaixo. Os serviços disponíveis são: `BirdID`, `SafeID`, `NeoID` e `VIDaaS`:
* **<provider>:ClientId**: o client id deste provedor.
* **<provider>:ClientSecret**: o client secret deste provedor.
* **<provider>:Endpoint** (opcional): só deve ser definido para provedores fora do padrão, como ambientes de homologação. Especifica o endpoint do serviço.
* **<provider>:BadgeUrl** (opcional): só deve ser definido para provedores fora do padrão, como ambientes de homologação. Especifica a imagem do selo que será exibida
ao usuário.
* **<provider>:Provider** (opcional): só deve ser definido para provedores fora do padrão, como ambientes de homologação. Especifica o nome do provedor.
* **<provider>:ProtocolVariant** (opcional): só deve ser definido para provedores fora do padrão, como ambientes de homologação. Especifica o nome da variante do protocolo.
* **DiscoveryEnabled** (padrão: `true`, v1.48.1): se `false`, desabilita a descoberta de provedores de certificados em nuvem (exibe todos os provedores configurados para todos os usuários).
### Configurações *VisualRepresentation*
Na seção **VisualRepresentation**:
* **Enabled** (padrão: `true`, v1.7.0): se `true`, todos os documentos PDF assinados com PAdES terão uma representação visual da assinatura, que pode ser posicionada automática
ou manualmente.
* **SignatureWidthCentimetersOnA4**: largura da representação visual da assinatura, em centímetros (otimizada para o tamanho de papel A4). O valor padrão, a partir da v1.37.0, é `5.85` (o valor anterior era `6`).
* **SignatureHeightCentimetersOnA4**: altura da representação visual da assinatura, em centímetros (otimizada para o tamanho de papel A4). O valor padrão, a partir da v1.37.0, é `1.45` (o valor anterior era `3.3`).
* **SignatureInitialsWidthCentimetersOnA4**: largura da representação visual da rubrica (usada ao aprovar um documento), em centímetros (otimizada para o tamanho de papel A4). O valor padrão é `1.5`.
* **SignatureInitialsHeightCentimetersOnA4**: altura da representação visual da rubrica (usada ao aprovar um documento), em centímetros (otimizada para o tamanho de papel A4). O valor padrão é `1.5`.
* **AuthenticationStampWidthCentimetersOnA4**: largura da representação visual do selo de autenticação, em centímetros (otimizada para o tamanho de papel A4). O valor padrão é `2`.
* **AuthenticationStampHeightCentimetersOnA4**: altura da representação visual do selo de autenticação, em centímetros (otimizada para o tamanho de papel A4). O valor padrão é `2`.
* **AuthenticationSignatureWidthCentimetersOnA4**: largura da representação visual da assinatura de autenticação, em centímetros (otimizada para o tamanho de papel A4). O valor padrão é `19.31`.
* **AuthenticationSignatureHeightCentimetersOnA4**: altura da representação visual da assinatura de autenticação, em centímetros (otimizada para o tamanho de papel A4). O valor padrão é `3.436`.
* **DisableAutomaticSignaturePositioning** (v1.18.0): se `true`, a representação visual da assinatura não é adicionada ao documento se o usuário não
selecionar manualmente uma posição ao assinar.
* **SignaturePositioningRequired** (v1.28.0): se `true`, exige que o usuário posicione a representação da assinatura ao assinar um arquivo PDF.
* **SignaturePositioningEnabledByDefault** (v1.52.0): se `true`, a opção de pré-posicionar a assinatura vem selecionada por padrão na tela de criação de documento.
* **TextVerticalMarginMultiplier** (padrão: `1.0`, v1.37.0): define a margem vertical do texto multiplicando o número informado por 1% da altura da assinatura.
* **TextHorizontalMarginMultiplier** (padrão: `1.0`, v1.37.0): define a margem horizontal do texto multiplicando o número informado por 1% da largura da assinatura.
* **EnableSignaturePositionConfirmation** (v1.62.0): se `true`, exibe um diálogo pedindo confirmação quando o usuário posiciona uma assinatura.
### Configurações *ValidationStamp* (v1.7.0)
Na seção **ValidationStamp**:
* **Enabled**: se `true`, adiciona um carimbo a todos os documentos PDF depois do upload. O carimbo contém a informação de onde o documento foi
criado e de onde suas assinaturas podem ser validadas. É uma opção recomendada se a representação visual estiver desabilitada.
* **AddSimplifiedManifest** (v1.10.1): se `true`, adiciona um manifesto simplificado como última página do documento. Isso também impede que os fluxos dos documentos sejam
editados, já que o manifesto só contém os participantes adicionados na criação do documento.
* **AllowCustomization** (v1.63.0): se `true`, permite que as organizações personalizem o carimbo de validação.
* **ShowOnBottom**: se `true`, exibe o carimbo de validação na parte inferior de todas as páginas.
* **ShowOnRight** (padrão: `true`): se `true`, exibe o carimbo de validação à direita de todas as páginas.
* **HeightCentimeters**: a altura do carimbo, em centímetros. O valor padrão é `0.5`.
* **LeadingCentimeters**: o deslocamento horizontal (ou vertical, se o carimbo estiver à direita), em centímetros, a partir do início da página. O valor padrão é `1.5`.
* **TrailingCentimeters**: o deslocamento horizontal (ou vertical, se o carimbo estiver à direita), em centímetros, a partir do fim da página. O valor padrão é `1.5`.
* **MarginToPageCentimeters**: a margem, em centímetros, em relação à parte inferior da página (ou ao lado direito, se o carimbo estiver à direita). O valor padrão é `0.3`.
* **SummaryFontSize**: o tamanho da fonte do carimbo, em pontos. O valor padrão é `8`.
* **ShowLogo** (v1.17.0): se `true`, exibe a logo no canto inferior direito de todas as páginas.
* **LogoWidthCentimeters** (v1.17.0): largura da logo, em centímetros. O valor padrão é `1`.
* **LogoHeightCentimeters** (v1.17.0): altura da logo, em centímetros. O valor padrão é `1`.
* **LogoMarginRightCentimeters** (v1.17.0): a margem da logo, em centímetros, em relação à parte inferior da página. O valor padrão é `1`.
* **LogoMarginBottomCentimeters** (v1.17.0): a margem da logo, em centímetros, em relação ao lado direito da página. O valor padrão é `1`.
* **EnableEditFlow** (v1.17.0): se `true`, permite pequenas alterações no fluxo do documento após o envio, mesmo que **AddSimplifiedManifest** também seja `true`. As alterações permitidas são:
adicionar ou remover observadores e atualizar os e-mails dos participantes (se permitido por outras configurações).
* **AllowOriginalFileDownload** (v1.60.0, padrão: `true`): se `true`, baixa a versão original de um documento, sem nenhuma das marcas do carimbo de validação.
* **DisplayOriginalFileWhileDocumentNotConcluded** (v1.60.0): se `true`, exibe o documento original na pré-visualização ou na assinatura de documentos (desde que eles
não estejam concluídos). Para que esta opção funcione corretamente, `AllowOriginalFileDownload` também precisa estar habilitada.
### Configurações *PrinterFriendly*
Na seção **PrinterFriendly**:
* **ShowSummaryBottom** (padrão: `true`): se `true`, exibe o resumo de assinaturas na parte inferior de todas as páginas.
* **ShowSummaryRight**: se `true`, exibe o resumo de assinaturas à direita de todas as páginas.
* **SummaryHeightCentimeters**: a altura do resumo, em centímetros. O valor padrão é `2`.
* **SummaryLeadingCentimeters**: o deslocamento horizontal (ou vertical, se o resumo estiver à direita), em centímetros, a partir do início da página. O valor padrão é `1.5`.
* **SummaryTrailingCentimeters**: o deslocamento horizontal (ou vertical, se o resumo estiver à direita), em centímetros, a partir do fim da página. O valor padrão é `3.5`.
* **SummaryMarginToPageCentimeters** (v1.6.0): a margem, em centímetros, em relação à parte inferior da página (ou ao lado direito, se o resumo estiver à direita). O valor padrão é `0.3`.
* **SummaryFontSize** (v1.6.0): o tamanho da fonte do resumo, em pontos. O valor padrão é `8`.
* **ShowLogo** (padrão: `true`): se `true`, exibe a logo no canto inferior direito de todas as páginas.
* **LogoWidthCentimeters**: largura da logo, em centímetros. O valor padrão é `1`.
* **LogoHeightCentimeters**: altura da logo, em centímetros. O valor padrão é `1`.
* **LogoMarginRightCentimeters**: a margem da logo, em centímetros, em relação à parte inferior da página. O valor padrão é `1`.
* **LogoMarginBottomCentimeters**: a margem da logo, em centímetros, em relação ao lado direito da página. O valor padrão é `1`.
* **ShowQrCode** (padrão: `true`): se `true`, exibe um QR code que leva à tela de validação das assinaturas do documento.
* **ManifestPageMargin**: margens da página do manifesto, em centímetros. O valor padrão é `2.5`.
* **ManifestPageHeight**: a altura da página do manifesto, em centímetros. O valor padrão é `29.7` (A4).
* **NormalFontSize**: tamanho da fonte normal na página do manifesto, em pontos. Os tamanhos das fontes dos cabeçalhos são definidos com base neste tamanho. O valor padrão é `12`.
* **SmallFontSize**: tamanho da fonte pequena na página do manifesto, em pontos. O valor padrão é `10`.
* **LineHeight**: altura aproximada de uma fonte com o tamanho de fonte normal. O valor padrão é `0.74`.
* **SmallLineHeight**: altura aproximada de uma fonte com o tamanho de fonte pequeno. O valor padrão é `0.6`.
* **AllowCustomization** (v1.63.0, padrão: `true`): se `true`, permite que as organizações personalizem a versão para impressão.
* **IgnorePreExistingSignatures** (v1.68.0, padrão: `true`): se `true`, não inclui (na versão para impressão) as assinaturas feitas antes da criação do documento
(veja também a configuração `PreExistingSignaturesDocumentCreationTresholdInSeconds`).
Isso é especialmente importante se os tipos de assinatura estiverem habilitados, pois as assinaturas existentes podem não corresponder ao tipo de assinatura selecionado quando o documento é enviado ao Signer. Se os tipos
de assinatura estiverem habilitados e você quiser desabilitar esta configuração, considere habilitar também `EnableSignatureValidationWhenGeneratingManifest`.
* **PreExistingSignaturesDocumentCreationTresholdInSeconds** (v1.68.1, padrão: `60`): define um limite de quão antigas as assinaturas podem ser para ainda serem consideradas feitas
após a criação do documento (definido em segundos a partir da data de criação do documento).
* **EvidenceImagesWatermarkText** (v1.77.0): se `true`, adiciona marca d'água às imagens de evidência exibidas na versão para impressão.
* **ShowIdScanOnPrinterFriendlyVersion** (v1.77.0): se `true`, exibe a leitura do documento de identidade (ID scan) na versão para impressão de uma assinatura eletrônica que exigiu essa autenticação.
* **AddWatermarkToSelfies** (v1.77.0): se `true`, adiciona marca d'água às selfies exibidas na versão para impressão.
* **EnableSingleSignerSummary** (v2.3.0): se `true` e o documento tiver apenas um signatário, o resumo exibe mais informações sobre o signatário.
* **EnableSingleSignerSummaryForMultipleSigners** (v2.3.0): se `true`, ignora o número de signatários e exibe o resumo de signatário único mesmo que haja mais de um signatário.
* **SingleSignerDateFormat** (v2.3.0): define o formato usado para exibir a assinatura no resumo de signatário único.
* **EmailAttachmentSignersLimit** (padrão: `30`, v2.14.0): número máximo de signatários em um fluxo acima do qual a versão para impressão deixa de ser anexada aos e-mails de notificação.
### Configurações *PaymentGateway* (v1.27.0)
Na seção **PaymentGateway**:
* **Enabled**: se `true`, habilita a integração com o gateway de pagamento.
* **Gateway** (padrão: `Iugu`): define o gateway de pagamento. O único valor disponível é `Iugu` ([https://www.iugu.com/](https://www.iugu.com/)).
* **DueDays** (padrão: `10`): o número de dias que o usuário terá para pagar uma fatura.
* **InvoiceProductName** (padrão: o mesmo valor da configuração `SiteName`): define o nome do produto que aparece no PDF da fatura.
* **MinInvoiceValue** (padrão: `1`): o valor mínimo de uma fatura. Todas as faturas com valor inferior a este são marcadas automaticamente como pagas depois de fechadas.
* **InvoiceDueDays** (padrão: `1`, v1.53.0): o número de dias após o fechamento da fatura usado para definir a data de vencimento da fatura. Isso controla quando são feitas as cobranças
automáticas no cartão de crédito.
* **BillingPlanDueDays** (padrão: `2`, v1.53.0): o número de dias que o usuário terá para fazer o pagamento da contratação de um plano de cobrança.
### Configurações *Iugu* (v1.27.0)
Na seção **Iugu**:
* **AccountId**: o ID da conta na Iugu.
* **ApiToken**: o token de API da Iugu.
* **WebhooksAuthKey**: a chave de autenticação de webhooks da Iugu.
* **TestMode**: define se as credenciais são do ambiente de testes.
* **PaymentMethods** (padrão: `credit_card,bank_slip,pix`, v1.37.0): lista dos meios de pagamento permitidos, separados por vírgula.
### Configurações *Invoice Receipt* (v1.27.0)
Na seção **InvoiceReceipt**:
* **Enabled**: se `true`, habilita a emissão de notas fiscais para as faturas.
* **Gateway** (padrão: `NFEio`): define o gateway de notas fiscais. O único valor disponível é `NFEio` ([https://nfe.io/](https://nfe.io/)).
* **ReceiptType** (padrão: `NFSe`): define o tipo de nota fiscal emitida. O único valor disponível é `NFSe` (nota fiscal de serviço).
* **CityServiceCode**: o código do serviço no município em que ele será prestado. Acesse "Empresas > Lista de Serviços Cadastrados" na sua conta da NFEio para ver os códigos disponíveis para a sua empresa.
* **CnaeCode** (opcional): o [CNAE](https://receita.economia.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/classificacao-nacional-de-atividades-economicas-2013-cnae) da sua empresa.
* **ReceiptDescription** (padrão: o mesmo valor da configuração `SiteName`): uma descrição incluída na "Descrição do Serviço" da nota fiscal.
### Configurações *SMS*
Na seção **SMS**:
* **Type**: define o provedor de SMS. As opções disponíveis são `TotalVoice`, `Twilio`, `Zenvia` (v1.28.0), `SmsEmpresa` (v1.28.0), `Generic` (v1.36.0), `AmazonSNS` (v1.55.0) e `Tigo` (v1.55.0).
Para o provedor `TotalVoice`, defina a configuração:
* **AccessToken**:
Para o provedor `Twilio`, defina as configurações:
* **MessageFrom**: o número usado para enviar a mensagem.
* **AccountSid**: o SID da conta.
* **AuthToken**: o Auth Token da conta.
Para o provedor `Zenvia`, defina as configurações:
* **Account** (v1.28.0): a conta de API do Zenvia Connect.
* **Password** (v1.28.0): a senha de API do Zenvia Connect.
Para o provedor `SmsEmpresa`, defina a configuração:
* **ChaveKey** (v1.28.0): a "Chave Key" exibida nas configurações da sua conta da SMS Empresa.
Para o provedor `Generic` (v1.36.0), defina as configurações:
* **Endpoint**: o endpoint para o qual as informações do SMS serão enviadas.
* **AuthType** (padrão: `NoAuth`): define o tipo de autorização informado na requisição. As opções disponíveis são `NoAuth`, `Basic`, `Bearer` e `ApiKey`.
* **Username**: o nome de usuário, se o AuthType for `Basic`.
* **Password**: a senha, se o AuthType for `Basic`.
* **BearerToken**: o token Bearer, se o AuthType for `Bearer`.
* **ApiKey**: a chave de API, se o AuthType for `ApiKey`.
Para o provedor `AmazonSNS` (v1.55.0), defina as configurações:
* **Region**: o código da região da AWS, por exemplo `sa-east-1` ou `us-east-1` (veja [AWS region codes](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html)).
* **AccessKeyId**: o ID da chave de acesso.
* **SecretAccessKey**: a chave de acesso secreta.
### Configurações *Whatsapp* (v1.56.0)
Na seção **Whatsapp**:
* **Type** (padrão: `Meta`, 1.78.0): define o serviço usado para enviar as mensagens de Whatsapp. As opções disponíveis são `Meta` e `Zenvia` (v1.78.0).
* **TemplateMapping** (opcional): dicionário de nomes de templates. Só deve ser usado se você não quiser registrar o template no Meta Business Management
com o nome que recomendamos. Exemplo:
```ini
Whatsapp__TemplateMapping__pendingsignature=signer_pendingsignature
```
Para o serviço `Meta`, defina as configurações:
* **Endpoint** (padrão: `https://graph.facebook.com`): o endpoint para o qual as mensagens de Whatsapp serão enviadas. Só deve ser definido se você tiver uma instância on-premises do
Whatsapp Business.
* **AccessToken**: um token de acesso da API do Whatsapp Business.
* **PhoneNumberId**: o ID do número de telefone que será usado para enviar as mensagens.
Para o serviço `Zenvia` (1.78.0), defina as configurações:
* **Endpoint** (padrão: `https://api.zenvia.com`): o endpoint da Zenvia para o qual as mensagens de Whatsapp serão enviadas.
* **ApiToken**: o token de API da Zenvia.
* **PhoneNumber**: o número de telefone que será usado para enviar as mensagens.
* **LanguageTemplateMap** (opcional): dicionário de nomes de template/idioma. Como a Zenvia usa GUIDs como nomes de templates e cada template tem um idioma fixo, use esta configuração para
mapear templates/idiomas para os GUIDs da Zenvia. Exemplo:
```ini
Whatsapp__LanguageTemplateMap__pendingsignature-pt_BR=c5d3904f-3e22-49bc-a92b-37eeb6522c90
Whatsapp__LanguageTemplateMap__authenticationcode-pt_BR=8bc863e9-e661-43dc-ab29-016b0b990b86
```
* **EnableSendingRemindersViaLinkSharingOption** (v2.3.0): se `true`, habilita o envio de lembretes diretamente pela API do WhatsApp.
* **MinimumWaitTimeBetweenRemindersInSeconds** (v2.3.0, padrão: `300`): tempo mínimo, em segundos, que deve ser aguardado entre as notificações de lembrete, para evitar spam.
### Configurações *NFEio* (v1.27.0)
Na seção **NFEio**:
* **ApiKey**: a chave de API da NFEio.
* **CompanyId**: o ID da empresa na NFEio a ser usado.
* **WebhookSecretKey**: a chave secreta usada ao criar webhooks na NFEio.
### Configurações *Zoom* (v1.29.0)
Na seção **Zoom**:
* **Enabled**: se `true`, habilita a integração com as videoconferências do Zoom.
* **JwtToken** (obsoleta desde a v1.59.0): o token de autenticação JWT do Zoom. Use a autenticação Server to Server, definindo `AccountId`, `ClientId` e `ClientSecret`.
* **AccountId** (v1.59.0): o ID da sua conta do Zoom. Obrigatório para a autenticação Server to Server.
* **ClientId** (v1.59.0): o client ID do Zoom. Obrigatório para a autenticação Server to Server.
* **ClientSecret** (v1.59.0): o client secret do Zoom. Obrigatório para a autenticação Server to Server.
### Configurações *Datavalid* (v1.32.0)
Na seção **Datavalid**:
* **Disabled**: se `true`, desabilita o serviço Datavalid.
* **ConsumerKey**: a consumer key do SERPRO para uso do serviço Datavalid.
* **ConsumerSecret**: o consumer secret do SERPRO para uso do serviço Datavalid.
* **ApiVersion** (v1.65.0, padrão: `v3`): versão da API a ser usada. Os valores disponíveis são `v3` e `v2`.
* **UseFacialOnlyValidation** (v1.65.0, padrão: `true`): se `true`, usa a API que valida apenas imagens faciais.
### Configurações *Gerencianet* (v1.32.0)
Na seção **Gerencianet**:
* **Disabled**: se `true`, desabilita o serviço Gerencianet.
* **ClientId**: o Client ID da API do serviço Gerencianet.
* **ClientSecret**: o Client Secret da API do serviço Gerencianet.
* **PixKey**: a chave Pix aleatória cadastrada na conta da Gerencianet.
* **CertificateBase64**: o certificado de autenticação (no formato Base64) do serviço Gerencianet. Se você hospeda a aplicação em um Azure App Service,
também precisa incluir a configuração `WEBSITE_LOAD_USER_PROFILE` com o valor `1`. Se hospeda no IIS (Windows Server), defina a opção **Carregar Perfil do Usuário**
da identidade do pool de aplicativos como `True`.
### Configurações *Batches* (v1.30.0)
Na seção **Batches**:
* **MaxDocumentsInBatch** (padrão: `50000`): o número máximo de documentos processados em um lote.
* **MaxItemsRetrievedFromDatabase** (padrão: `10000`): o número máximo de documentos retornados em uma única consulta ao banco de dados ao processar lotes. Em lotes de grande
volume, isso é usado para evitar timeouts nas consultas ao banco de dados.
* **NumberDocumentsPerSignatureRound** (padrão: `100`): número de documentos processados em cada rodada de um lote de assinatura.
* **NumberDocumentsPerGenerationRound** (padrão: `10`): número de documentos processados em cada rodada de um lote de geração.
* **NumberNotificationsCreatedPerRound** (padrão: `30`): número de notificações criadas por rodada do lote.
* **NumberDocumentsPerExpirationRound** (v1.33.0, padrão: `100`): número de documentos processados por rodada de expiração.
* **NumberDocumentsPerDocumentsCreatedWebhookRound** (v1.50.0, padrão: `100`): número de documentos retornados em cada rodada ao gerar o webhook de documentos criados.
* **NotifyPendingActionsIndividually** (v1.34.0): se `true`, envia os e-mails de ação pendente individualmente ao executar ações em lote.
* **NotifyParticipantsDefaultValue** (v1.39.0, padrão: `true`): define o valor padrão da opção de notificar participantes exibida quando um usuário assina documentos em lote.
* **MaxAgeForOnGoingDocumentBatchesInDays** (v1.80.1, padrão: `5`): a idade máxima dos lotes de documentos em andamento retornados na página de documentos.
* **MaxOnGoingDocumentBatchesItemsRetrievedFromDatabase** (v1.80.1, padrão: `10`): o número máximo de lotes de documentos retornados na página de documentos.
### Configurações *Envelopes* (v1.33.0)
Na seção **Envelopes**:
* **MaxNumberFiles** (padrão: `20`): o número máximo de arquivos no envelope.
### Configurações *Custom Resources* (v1.37.0)
Na seção **CustomResources**:
* **Localizers**: dicionário de localizadores personalizados. As chaves disponíveis são: `MailFormatter` e `NotificationRepository`.
* **Resources**: dicionário de recursos a personalizar. A chave do dicionário é a chave do recurso.
* **en**: o valor do recurso específico para inglês.
* **pt**: o valor do recurso específico para português.
* **es**: o valor do recurso específico para espanhol.
* **Default**: o valor padrão do recurso, usado se o valor específico do idioma não for encontrado.
Exemplo:
```ini
CustomResources__Localizers__MailFormatter__Resources__SignDocument__Default=Sign Document
```
### Configurações *Reminder* (v1.49.0)
Na seção **Reminder**:
* **DefaultReminderPeriodInDays** (padrão: `3`): o número de dias a aguardar antes de enviar notificações de lembrete de ação para os documentos da organização. Se definido como `0`, as notificações de lembrete não são enviadas.
* **MaxNumberOfNotificationsForReminder** (padrão: `10`): o número máximo de notificações enviadas para um lembrete.
* **MaxNumberOfRemindersPerScheduleJobRound** (padrão: `100`): o número máximo de lembretes processados em uma rodada do job agendado.
### Configurações *Push Notification* (v1.54.0)
Na seção **PushNotification**:
* **Disabled**: se `true`, desabilita o envio de notificações push.
* **ApiKey**: uma chave de API de uma subscription no portal de clientes da Lacuna com permissão para enviar notificações push para o aplicativo Web PKI.
### Configurações *Access Control* (v1.63.0)
Na seção **AccessControl**:
* **IsOpen**: se `true`, qualquer usuário que entre ou se cadastre no site pode usar a aplicação e criar documentos na sua conta pessoal. Se `false`, os usuários só
poderão usar a área logada se tiverem sido adicionados previamente a uma organização. Veja [Controle de acesso](https://docs.lacunasoftware.com/articles/signer/on-premises/access-control) para mais detalhes.
* **BlockPersonalAccounts**: se `true` e o controle de acesso estiver aberto, os usuários podem usar a aplicação normalmente, mas precisam criar ou usar uma organização para executar ações.
### Configurações *Email* (v1.66.0)
Na seção **Email**:
* **EnableTracking** (padrão: `true`): se `true`, os e-mails são rastreados, e as evidências de abertura e de cliques em links são registradas.
* **EnableUnsubscribe**: se `true`, adiciona aos e-mails um link de descadastramento. Os endereços de e-mail que se descadastraram só voltam a receber notificações se forem retirados da
lista de bloqueio pelo administrador da instância.
### Configurações *Signature Types* (v1.68.0)
Na seção **SignatureTypes**:
* **Enabled**: se `true`, habilita a seleção de um tipo de assinatura ao criar e validar documentos.
* **DefaultType** (padrão: `Advanced`): define a opção de tipo de assinatura padrão ao criar e validar documentos. As opções disponíveis são: `Simple`, `Advanced` ou `Qualified`.
* **Simple**: subseção que define as configurações relativas ao tipo de assinatura `Simple`:
* **Disabled**: se `true`, desabilita o tipo de assinatura Simple.
* **AllowAdvancedElectronicSignatures**: se `true`, as assinaturas eletrônicas avançadas são validadas com sucesso ao validar um documento com o tipo de assinatura `Simple`.
* **TrustArbitrator** (padrão: árbitro que aceita a ICP-Brasil): configurações de trust arbitrator específicas do tipo de assinatura `Simple`. Podem ser definidas como explicado em
[Configurações de Trust Arbitrator](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-trust-arbitrator). Veja também [TrustArbitrators dos tipos Simples e Qualificada](https://docs.lacunasoftware.com/articles/signer/on-premises/trust-arbitrator#trustarbitrators-dos-tipos-simples-e-qualificada).
* **Authentication**: subseção que define os tipos de autenticação disponíveis para o tipo de assinatura `Simple`. Quando não definida, os tipos disponíveis são definidos pelas
[configurações de ElectronicSignature](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-electronicsignature).
* **EnableSmsAuthentication**
* **EnableWhatsappAuthentication**
* **EnableOtpAuthentication**
* **EnableSelfieAuthentication**
* **EnableDatavalidAuthentication**
* **EnablePixAuthentication**
* **EnableLivenessAuthentication** (v1.75.0)
* **EnableIdScanAuthentication** (v1.77.0)
* **RequireLivenessOnSelfieAuthentication** (v1.75.0)
* **RequireLivenessOnDatavalidAuthentication** (v1.75.0)
* **Advanced**: subseção que define as configurações relativas ao tipo de assinatura `Advanced`:
* **DisplaySecurityContextNames**: se `true`, adiciona o nome do contexto de segurança da assinatura Advanced ao exibir o tipo de assinatura. Só deve ser definida se você tiver mais de um
contexto de segurança.
* **AllowSimpleElectronicSignatures**: se `true`, as assinaturas eletrônicas simples são validadas com sucesso ao validar um documento com o tipo de assinatura `Advanced`.
* **Qualified**: subseção que define as configurações relativas ao tipo de assinatura `Qualified`:
* **TrustArbitrator** (padrão: árbitro que aceita a ICP-Brasil): configurações de trust arbitrator específicas do tipo de assinatura `Qualified`. Podem ser definidas como explicado em
[Configurações de Trust Arbitrator](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-trust-arbitrator). Veja também [TrustArbitrators dos tipos Simples e Qualificada](https://docs.lacunasoftware.com/articles/signer/on-premises/trust-arbitrator#trustarbitrators-dos-tipos-simples-e-qualificada).
### Configurações *Totals* (v1.72.0)
Na seção **Totals**:
* **HideDocumentPaginationTotalTreshold** (padrão: `10000`): limite para ocultar a contagem da lista de documentos.
* **HideDocumentPaginationTotal** (padrão: `true`): se `true`, oculta o total ao listar documentos se o total ultrapassar o `HideDocumentPaginationTotalTreshold`.
* **HideOrganizationDocumentTotalsTreshold** (padrão: `10000`): limite para ocultar o total de documentos nos detalhes da organização.
* **HideOrganizationDocumentTotals**: se `true`, oculta o total de documentos nos detalhes da organização se o total ultrapassar o `HideOrganizationDocumentTotalsTreshold`.
* **HideOrganizationDocumentPaginationTotalsTreshold** (padrão: `10000`): limite para ocultar a contagem da lista de documentos na administração da organização.
* **HideOrganizationDocumentPaginationTotals**: se `true`, oculta o total ao listar documentos na administração da organização se o total ultrapassar o `HideOrganizationDocumentPaginationTotalsTreshold`.
* **HideEventPaginationTotalTreshold** (padrão: `10000`): limite para ocultar a contagem da lista de eventos.
* **HideEventPaginationTotal** (padrão: `true`): se `true`, oculta o total ao listar eventos se o total ultrapassar o `HideEventPaginationTotalTreshold`.
### Configurações *Throttling*
Na seção **Throttling**:
* **EnableSensitiveDataLogging**: se `true`, registra no log informações sensíveis sobre as requisições limitadas, como o endereço IP.
* **DocumentKeyThrottling**: define os parâmetros de throttling das consultas por chave de documento. Veja `Throttling Parameters` abaixo. Vem habilitado por padrão, com limite `10` e intervalo `1`.
### Configurações *Throttling Parameters*
Define as seguintes propriedades:
* **Enabled**: se `true`, habilita este tipo de throttling.
* **IntervalInSeconds**: o intervalo, em segundos, durante o qual as operações sujeitas a throttling são avaliadas.
* **Limit**: o número máximo de operações que podem ser executadas antes que o throttling seja aplicado.
### Configurações *Trust Arbitrator*
> **Dica**
>
> Para um passo a passo com exemplos, veja [TrustArbitrator](https://docs.lacunasoftware.com/articles/signer/on-premises/trust-arbitrator).
As configurações de trust arbitrator permitem definir quais certificados digitais são aceitos pela aplicação. Isso é feito definindo um JSON no seguinte formato:
```json
{
"Version": "2019-05-09",
"TrustedRoots": [],
"StandardPkis": []
}
```
* **Version**: versão do formato. As opções disponíveis são `2019-05-09` ou `2022-07-21`. A última também aceita a propriedade `IntermediateCAs`.
* **StandardPkis**: lista de PKIs padrão. As opções disponíveis são `Brazil`, `Italy`, `Peru`, `System`, `ENotariado` e `RegistroCivil`. `System` aceita as raízes confiáveis do sistema operacional do servidor.
* **TrustedRoots**: lista de certificados raiz no formato Base64.
* **IntermediateCAs** (somente com a versão `2022-07-21`): lista de certificados de ACs intermediárias no formato Base64. Útil quando os certificados dos signatários não trazem as informações necessárias para
baixar as ACs intermediárias da cadeia. Esses certificados não são confiáveis por si só: a cadeia ainda precisa terminar em uma raiz aceita.
Na seção **TrustArbitrator**:
* **Type** (padrão: árbitro que aceita a ICP-Brasil): define de onde o sistema obtém o JSON do trust arbitrator. As opções disponíveis são `AppSetting` ou `BlobStorage`.
* **Content**: deve ser definida se `AppSetting` for selecionado. O conteúdo JSON do trust arbitrator. Se esta configuração for definida em um arquivo de configuração JSON, lembre-se de que as aspas duplas
precisam ser escapadas. Exemplo:
```json
"Content": "{ \"Version\": \"2019-05-09\", \"StandardPkis\": [ \"Brazil\" ] }"
```
* **BlobName**: deve ser definida se `BlobStorage` for selecionado. O nome do arquivo com o conteúdo JSON do trust arbitrator armazenado no Blob Storage. Exemplo: `"BlobName": "my-arbitrator.json"`.
* **BlobFolderName** (padrão: `trustarbitrators`): a pasta do blob em que fica o arquivo com o conteúdo JSON do trust arbitrator armazenado no Blob Storage. Só é relevante se `BlobStorage` for selecionado.
### Configurações *Security Headers* (v2.1.0)
Na seção **SecurityHeaders**:
* **Default__Enabled**: se `true`, habilita o uso de headers de segurança.
### Configurações *Terms Of Use* (v2.3.0)
Na seção **TermsOfUse**:
* **SendTermsOfUseUpdateOnlyToSubscriptionUsers**: se `true`, restringe o envio das atualizações dos termos de uso aos usuários de subscriptions.
* **ProcessingDelayInMinutes** (padrão: `20`): define o intervalo, em minutos, a ser aguardado entre as notificações de termos de uso.
* **MaxItemsRetrievedFromDatabase** (padrão: `10000`): define o número máximo de usuários retornados do banco de dados para notificar sobre alterações nos termos de uso.
### Configurações *SigningTags*
Na seção **SigningTags**:
* **PageHeight**:
* **PageWidth**:
* **PageMarginTop**:
* **PageMarginRight**:
* **PageMarginBottom**:
* **PageMarginLeft**:
### Configurações *Amplia*
Na seção **Amplia**:
* **EndpointUrl**:
* **ApiKey**:
* **EnableAttributeCertificate**:
* **KeyStore**: o valor padrão é `Database`.
### Configurações *Hsm*
Na seção **Hsm**:
* **Enabled**:
* **EnableLocalCA**: se `true`, permite que os usuários emitam certificados de teste usando o serviço local do Amplia.
* **ImageUrl**:
* **Name**:
* **Type**:
* **DinamoPocket**: subseção:
#### Configurações *DinamoPocket*
* **Address**: subseção:
* **MasterUser**: subseção:
* **MasterPassword**: subseção:
### Configurações *Timestamper*
Na seção **Timestamper**:
* **Enabled**: se `true`, carimbos de tempo serão adicionados às assinaturas.
* **Url**: a URL do servidor de carimbo de tempo.
* **Type** (padrão: `BearerToken`, v1.28.0): define o tipo de autenticação usado para conectar ao servidor de carimbo de tempo. As opções disponíveis são `BearerToken`, `BasicAuthentication` (obsoleto desde a v1.43.0) e `BasicAuthenticationV2` (v1.43.0).
* **BearerToken**: se o tipo for `BearerToken`, define o valor do token de portador para a autenticação.
* **Username** (v1.28.0): se o tipo for `BasicAuthentication` ou `BasicAuthenticationV2`, define o nome de usuário para a autenticação.
* **Password** (v1.28.0): se o tipo for `BasicAuthentication` ou `BasicAuthenticationV2`, define a senha para a autenticação.
* **MaxAutoRetryCount** (padrão: `3`, v1.30.0): o número de novas tentativas feitas se não for possível obter um carimbo de tempo na primeira tentativa.
* **RetryDelayInMilliseconds** (padrão: `1500`): o tempo de espera, em milissegundos, entre as novas tentativas de obter o carimbo de tempo.
* **Organization** (v2.22.0): controla como os administradores de organização podem gerenciar a configuração de carimbo de tempo de cada organização:
* **EnabledByDefault** (padrão: `true`, v2.22.0): o valor usado na configuração de carimbo de tempo de uma organização quando ela não tem um valor próprio definido explicitamente.
* **DisableSettingsByNonInstanceAdmin** (padrão: `false`, v2.22.0): quando `true`, somente os administradores do sistema da instância podem alterar (ou redefinir) a configuração de carimbo de tempo de uma organização; a seção fica oculta para os administradores de organização, e qualquer tentativa de alterá-la pela API é rejeitada com `403 Forbidden`.
### Configurações *Notarize*
Na seção **Notarize**:
* **Enabled**:
* **Notaries**: lista de subseções:
#### Configurações *NotaryConfig*
* **Type**: subseção:
* **ApplicationId**: subseção:
* **NotaryApplicationKey**: subseção:
* **EndpointUrl**:
* **UploadApi**:
* **StartNotarizationApi**:
### Configurações *Authenticator*
Na seção **Authenticator**:
* **HashAlgorithm**:
* **NumberOfDigits**:
* **PeriodInSeconds**:
* **TokenAuthTimeValidationEnabled**:
* **TokenAuthTimeValidation**:
### Configurações *Scanner*
Na seção **Scanner**:
* **Disabled**: se `true`, desabilita a integração com o Scanner.
* **Endpoint**: o endpoint do Scanner.
* **ApiKey**: uma chave de API do Scanner. Para habilitar a validação de documentos de todas as subscriptions do Scanner, informe uma chave com privilégios de Root Admin e defina o parâmetro `SubscriptionId`.
* **SubscriptionId** (opcional): só deve ser definida se a `ApiKey` for uma chave Root. Define a subscription à qual os documentos digitalizados serão associados.
* **InputType** (padrão: 'Scan', v1.37.0): define as opções de entrada permitidas. As opções disponíveis são `Scan`, `FileUpload` e `ScanOrFileUpload`.
* **MetadataInputEnabled** (padrão: `true`, v1.37.0): se `true`, o usuário que digitaliza precisará informar os metadados.
* **MetadataPresets** (opcional, v1.37.0): subseção definida a seguir:
* **DocumentType**: define o tipo de documento nos metadados, para que o usuário que digitaliza não precise informá-lo ao preencher os metadados.
* **DocumentTypeIsReadonly**: se `true`, o usuário que digitaliza não poderá alterar o tipo de documento.
### Configurações *Google Tag Manager* (v1.50.0)
Na seção **GoogleTagManager**:
* **Id**: o ID do Tag Manager. Deve ser informado para usar o Tag Manager.
* **Auth**
* **Preview**
* **ResourcePath**
* **CspNonce**
### Configurações *Document Access Validation* (v2.7.0)
Na seção **DocumentAccessValidation**:
* **Enabled**: se `true`, a validação de acesso a documentos fica habilitada. Com ela habilitada, os usuários podem selecionar um tipo de validação ao criar um documento, e os usuários precisarão fazer a validação selecionada para acessar o documento na área pública de validação.
* **RequireForEveryDocumentAccess**: se `true`, todos os documentos exigirão validação, mesmo que o criador do documento não tenha selecionado nenhum tipo de validação ao criá-lo ou que o documento tenha sido criado antes de esta configuração ser habilitada. Se `false`, só os documentos que tiveram um tipo de validação selecionado na criação exigirão validação.
* **DefaultValidationType** (padrão: `UserIdentifier`): o tipo de validação usado para os documentos que não têm um tipo de validação selecionado explicitamente quando `RequireForEveryDocumentAccess` é `true`. No momento, a única opção disponível é `UserIdentifier`, que exige que o usuário informe o CPF de um signatário para acessar o documento, mas outros tipos de validação podem ser adicionados no futuro.
### Configurações *IdRC* (v2.8.0)
Estas configurações são para a integração com o serviço de assinatura do Registro Civil (IdRC). Na seção **Idrc**:
* **Enabled**: se `true`, habilita a integração com o serviço de assinatura do Registro Civil (IdRC). Isso é necessário para permitir que os usuários usem o IdRC como opção nas raízes confiáveis do contexto de segurança.
* **Homologation**: se `true`, habilita o modo de homologação (testes) da integração com o IdRC. Isso inclui as raízes dos certificados de homologação como raízes confiáveis quando o usuário adiciona o IdRC ao contexto de segurança.
* **IdrcServerBaseUrl**: a URL base do servidor do IdRC a ser usado.
* **IdrcServerApiKey**: a chave de API fornecida pelo IdRC.
* **IdrcServerClientSecret**: o client secret acordado com o IdRC.
* **IdrcServerClientId**: o client ID acordado com o IdRC.
### Configurações *IdentifierScope* (v2.11.0)
A partir da versão 2.11.0, é possível configurar uma instância para usar mais de um tipo de identificador. Ainda assim, é preciso ter um tipo de identificador principal, usado quando nenhum tipo é especificado, e definir o comportamento de unicidade de cada tipo de identificador. Essas configurações ficam na seção **IdentifierScope**.
Desde a versão 2.18.0, ao usar vários tipos de identificador, o modo de cada tipo é respeitado: um tipo cujo modo não seja Nullable ou NullableUnique é obrigatório sempre que esse tipo for selecionado para um usuário. Para exigir que um usuário tenha pelo menos um identificador de qualquer tipo permitido, use a configuração `RequireAtLeastOneIdentifier`.
Na seção **IdentifierScope**:
* **EnableMultipleIdentifiersPerUser**: se `true`, permite que os usuários tenham mais de um identificador (ex.: CPF e um identificador paraguaio simultaneamente).
* **AllowedIdentifierTypes**: lista, separada por vírgulas, dos tipos de identificador permitidos. Exemplo: `Cpf,EcuadorBceId`. Quando definida, só os tipos listados são aceitos. Tipos suportados: `Cpf`, `EcuadorBceId`, `ParaguayCedulaIdentidad`, `ParaguayRuc` e `ParaguayPassport`.
* **DefaultIdentifierType**: o tipo de identificador padrão, usado quando nenhum tipo é especificado explicitamente. Substitui a configuração obsoleta `General.IdentifierType`. Deve ser um tipo de identificador válido incluído na configuração `AllowedIdentifierTypes`. Exemplo: `Cpf`.
* **IdentifierModes**: lista, separada por vírgulas, de pares `IdentifierType=Mode` que definem o comportamento de unicidade de cada tipo de identificador. Substitui a configuração obsoleta `General.CountryIdentifierMode`. Os modos disponíveis são `Unique`, `NonUnique`, `Nullable` e `NullableUnique`. Exemplo: `Cpf=Nullable,EcuadorBceId=NullableUnique`. **Se `EnableMultipleIdentifiersPerUser` for `true`, esta configuração deve ser definida para todos os tipos de identificador incluídos em `AllowedIdentifierTypes`.** Desde a versão 2.18.0, tipos com modo não anulável são permitidos quando vários identificadores estão habilitados: o valor do identificador passa a ser obrigatório sempre que esse tipo for selecionado.
* **RequireAtLeastOneIdentifier** (padrão: `false`, v2.18.0): se `true`, todo usuário precisa ter pelo menos um identificador de um tipo permitido, mesmo quando todos os modos de identificador são anuláveis. Identificadores de tipos que não estão mais listados em `AllowedIdentifierTypes` não atendem a essa regra. Só pode ser usada quando `EnableMultipleIdentifiersPerUser` é `true`; caso contrário, a aplicação não inicia.
* **DisableGrantIdIdentifierSync** (v2.11.1, padrão: `false`): se `true`, desabilita a sincronização de identificadores com o GrantId. Isso é especialmente útil ao usar vários tipos de identificador, já que o GrantId só suporta um identificador por usuário.
* **DisableSyncIdentifiersJob** (v2.13.2, padrão: `false`): se `true`, desabilita o job em segundo plano que sincroniza periodicamente os identificadores dos usuários. Útil quando o job de sincronização causa conflitos ou não é necessário na implantação.
### Configurações *Contacts* (v2.12.0)
Na seção **Contacts**:
* **Disabled** (padrão: `false`): se `true`, desabilita completamente a funcionalidade de lista de contatos externos.
### Configurações *AdvancedSearch* (v2.12.0)
Na seção **AdvancedSearch**:
* **Disabled** (padrão: `false`): se `true`, desabilita a funcionalidade de busca avançada de participantes.
* **AllowIncludeContacts** (padrão: `true`): se `false`, remove a opção que permite aos usuários escolher se os contatos são incluídos nos resultados da busca.
* **DefaultIncludeContacts** (padrão: `true`): define se os contatos são incluídos nos resultados da busca por padrão.
* **AllowIncludeExternalUsers** (padrão: `false`): se `true`, exibe a opção que permite aos usuários escolher se os usuários externos são incluídos nos resultados da busca.
* **DefaultIncludeExternalUsers** (padrão: `false`): define se os usuários externos são incluídos nos resultados da busca por padrão.
### Configurações *RestPki* (v2.13.0)
Na seção **RestPki** (antes **FaceTec**; o nome antigo da seção continua aceito por compatibilidade):
* **Endpoint**: a URL do endpoint do serviço RestPki.
* **ApiKey**: a chave de API do serviço RestPki.
Obrigatória quando `EnableLivenessAuthentication` ou `EnableIdScanAuthentication` está definida como `true` na seção `ElectronicSignature`.
### Configurações *Pdf* (v2.14.0)
Na seção **Pdf**:
* **PdfAConversionEnabled** (padrão: `false`, v2.14.0): se `true`, habilita a conversão de documentos para PDF/A.
* **DefaultPdfAProfile** (padrão: `PdfA2b`, v2.14.0): o perfil PDF/A padrão usado na conversão de documentos. As opções disponíveis são: `PdfA1a`, `PdfA1b`, `PdfA2a`, `PdfA2b`, `PdfA3a` e `PdfA3b`.
* **PdfService**: subseção que define a conexão com o Lacuna PDF Service, usado para fazer a conversão para PDF/A. Obrigatória quando `PdfAConversionEnabled` é `true`:
* **Endpoint**: a URL do endpoint do PDF Service.
* **ApiKey**: a chave de API do PDF Service.
### Configurações *DocumentDownload* (v2.16.1)
Na seção **DocumentDownload**. Estas configurações ficavam antes na seção **General**, que agora está obsoleta para essas chaves; as chaves antigas de General continuam sendo respeitadas como alternativa quando a chave correspondente de `DocumentDownload` não está definida:
* **EnableSignatureMarksDownload** (padrão: `false`): se `true`, permite que o usuário baixe a versão com marcas de assinatura de um documento.
* **EnableSigningTagsDownload** (padrão: `true`): se `true`, permite que o usuário baixe a versão com signing tags de um documento.
* **EnablePrinterFriendlyDownload** (padrão: `true`): se `true`, permite que o usuário baixe a versão para impressão de um documento.
* **EnableRefusedDocumentDownload** (padrão: `false`, v2.16.1): se `false`, bloqueia o download de um documento depois que ele for recusado, impedindo que documentos recusados circulem como se fossem válidos e assinados. Defina como `true` para permitir o download de documentos recusados.
* **CanceledWatermarkText**: o texto da marca d'água adicionada aos documentos cancelados no download.
* **ExpiredWatermarkText**: o texto da marca d'água adicionada aos documentos expirados no download.
* **RefusedWatermarkText** (v2.16.1): o texto da marca d'água adicionada aos documentos recusados no download.
# [Personalização do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/customization)
O Signer pode ser personalizado em **dois níveis**, e essa distinção é o que determina quem faz cada
ajuste:
* **Instância**: vale para todo o Signer. Inclui tema de cores, logos, home page, textos das notificações
e domínio. São definições feitas na implantação ou pelo administrador do sistema.
* **Organização**: vale apenas para os documentos daquela organização. Inclui logo e cores dos e-mails,
logo na versão para impressão, carimbo de validação e imagem da representação visual. Cada
administrador de organização configura a sua, pela própria aplicação.
## O que dá para personalizar
| O que | Nível | Quem configura | Onde |
|---|---|---|---|
| Tema de cores da aplicação | Instância | Equipe de implantação | [Identidade visual](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/identidade-visual) |
| Logos da aplicação (4 arquivos) | Instância | Equipe de implantação | [Identidade visual](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/identidade-visual) |
| Home page (simples ou estática) | Instância | Equipe de implantação | [Home page e footer](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/home-e-footer) |
| Footer da home page | Instância | Equipe de implantação | [Home page e footer](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/home-e-footer) |
| Servidor SMTP próprio | Instância | Administrador do sistema | [Servidor de e-mail (SMTP)](https://docs.lacunasoftware.com/articles/signer/on-premises/email-smtp) |
| Textos de e-mails e notificações | Instância | Administrador do sistema | [E-mails e notificações](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/emails) |
| Provedor de SMS | Instância | Administrador do sistema | [Envio de SMS](https://docs.lacunasoftware.com/articles/signer/on-premises/sms) |
| Domínio próprio | Instância | Cliente (DNS) e implantação | [Domínio próprio](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/dominio) |
| Logo e cores dos e-mails | Organização | Admin da organização | [E-mails e notificações](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/emails#personalizacao-por-organizacao) |
| E-mail de suporte, prefixo do assunto e conteúdo extra dos e-mails | Organização | Admin da organização | [E-mails e notificações](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/emails#personalizacao-por-organizacao) |
| Logo e layout da versão para impressão | Organização | Admin da organização | [Documentos gerados](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/documentos) |
| Logo do carimbo de validação | Organização | Admin da organização | [Documentos gerados](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/documentos) |
| Imagem da representação visual da assinatura | Organização | Admin da organização | [Documentos gerados](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/documentos) |
## Nesta seção
* [Identidade visual](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/identidade-visual) trata do tema de cores e das logos.
* [Temas disponíveis](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/temas) traz a galeria completa, com o código de cada tema.
* [Home page e footer](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/home-e-footer) mostra as duas formas de personalizar a home.
* [E-mails e notificações](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/emails) cobre o branding dos e-mails e a customização de textos.
* [Documentos gerados](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/documentos) trata da versão para impressão, do carimbo de validação e da
representação visual da assinatura.
* [Domínio próprio](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/dominio) trata de servir a instância em um domínio seu.
> **Observação**
>
> Itens marcados como "equipe de implantação" não são autoatendidos. Entre em contato com a equipe
> informando o que deseja, como o código do tema, os arquivos de logo ou o HTML da home, para que os ajustes
> sejam aplicados à sua instância.
> **Dica: Canais de envio**
>
> A configuração dos canais de envio de notificações fica na seção de Integração:
> [Envio de SMS](https://docs.lacunasoftware.com/articles/signer/on-premises/sms), [Servidor de e-mail (SMTP)](https://docs.lacunasoftware.com/articles/signer/on-premises/email-smtp) e
> [Integração com WhatsApp](https://docs.lacunasoftware.com/articles/signer/on-premises/whatsapp).
# [Identidade visual](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/identidade-visual)
## Esquema de cores
A aplicação é tematizada por um tema com duas cores principais:
* **Theme**: cor do tema.
* **Accent**: cor de contraste.

Cada tema é identificado por um **código de três letras**, como `acr` para *amazon-cornell-red*. A lista
completa está em [Temas disponíveis](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/temas).
Para trocar o tema da sua instância, informe o código desejado à equipe de implantação.
> **Observação**
>
> Caso nenhum dos temas atenda, um novo tema pode ser criado sob medida. Basta informar as cores *theme* e
> *accent* desejadas.
O mesmo código de três letras é usado para tematizar o widget de
[Assinatura Embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature).
## Logos
A aplicação usa **quatro** arquivos de logo, servidos em `/theme-assets/`:
| Arquivo | Onde aparece |
|---|---|
| `light-logo` | Sobre fundo escuro, no menu lateral e na barra superior da área autenticada, e como logo padrão dos e-mails |
| `dark-logo` | Sobre fundo claro, no cabeçalho da área pública e nas telas externas |
| `stamp-logo` | Logo padrão da versão para impressão (manifesto de assinaturas) |
| `val-stamp-logo` | Logo padrão do carimbo de validação |
Exemplos de `light-logo` e `dark-logo`:


Os arquivos devem ser entregues à equipe de implantação, que os coloca na pasta `assets` do *Blob Storage*
configurado para a instância, o mesmo local usado pelos arquivos da [home page](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/home-e-footer). Depois
da atualização, a aplicação precisa ser reiniciada.
> **Dica**
>
> `stamp-logo` e `val-stamp-logo` definem apenas o **padrão da instância**. Cada organização pode enviar a
> sua própria logo para a versão para impressão e para o carimbo de validação sem depender da implantação,
> como mostra a página [Documentos gerados](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/documentos).
## Veja também
* [Temas disponíveis](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/temas)
* [Home page e footer](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/home-e-footer)
* [Documentos gerados](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/documentos)
# [Temas disponíveis](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/temas)
Cada tema é identificado por um **código de três letras**. Esse código é o valor informado à equipe de
implantação para trocar o tema da instância, e também o valor aceito pelo parâmetro `theme` do widget
de [Assinatura Embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature).
Além dos temas abaixo, existe o tema `default`:

## Lista
| Código | Tema | Desde |
|---|---|---|
| `apb` | amaranth-pacific-blue | |
| `acr` | amazon-cornell-red | |
| `alg` | azure-lime-green | |
| `bvr` | blue-venetian-red | v1.36.0 |
| `cgo` | castleton-green-orange | v1.10.1 |
| `clg` | cerulean-lime-green | |
| `cam` | charcoal-amazonite | |
| `cof` | charcoal-orange-flame | v1.26.1 |
| `ctv` | chartreuse-traditional-violet | v1.44.1 |
| `clc` | cobalt-lemon-curry | |
| `cpg` | cyan-process-green | v2.0.0 |
| `dcg` | dark-cerulean-green | |
| `dgy` | dark-grey-yellow | |
| `dir` | dark-indigo-red | |
| `dmg` | davys-maximum-green | v1.49.0 |
| `djp` | deep-jungle-princeton | v1.59.0 |
| `dpb` | dim-palatinate-blue | v1.58.1 |
| `epi` | electric-purple-indigo | v1.39.0 |
| `ecb` | eminence-cornflower-blue | v1.39.1 |
| `eva` | english-vermillion-arsenic | |
| `frv` | folly-russian-violet | v1.68.0 |
| `fbp` | french-blue-pink | v2.4.8 |
| `gvb` | generic-viridian-blue | v1.33.0 |
| `gdc` | green-dark-coral | |
| `gmc` | gun-metal-camel | v2.9.2 |
| `idg` | independence-green | |
| `ioa` | international-orange-apricot | v1.33.0 |
| `iog` | international-orange-green | v1.31.0 |
| `mse` | metallic-seaweed-emerald | |
| `oco` | onyx-carrot-orange | v1.31.0 |
| `osg` | onyx-satin-gold | |
| `obc` | oregon-blue-cadet | v2.0.0 |
| `obg` | oxford-blue-green | v1.10.1 |
| `pps` | persian-plum-sand | v1.10.4 |
| `pbg` | prussian-blue-green | v2.3.0 |
| `qbm` | queen-blue-mint | |
| `sgy` | sea-green-yellow | v2.0.0 |
| `seb` | silver-eerie-black | v1.54.0 |
| `scy` | space-cadet-yellow | v1.34.0 |
| `tbg` | teal-blue-gold | |
| `vgy` | viridian-green-yellow | v1.10.2 |
| `vsb` | vivid-sky-blue | v1.44.0 |
| `yby` | yale-blue-yellow | v1.51.0 |
| `zuo` | zomp-ut-orange | v1.56.0 |
> **Observação**
>
> Temas sem versão indicada existem desde as primeiras versões do Signer.
## Galeria
### amaranth-pacific-blue (`apb`)

### amazon-cornell-red (`acr`)

### azure-lime-green (`alg`)

### blue-venetian-red (`bvr`)

### castleton-green-orange (`cgo`)

### cerulean-lime-green (`clg`)

### charcoal-amazonite (`cam`)

### charcoal-orange-flame (`cof`)

### chartreuse-traditional-violet (`ctv`)

### cobalt-lemon-curry (`clc`)

### cyan-process-green (`cpg`)

### dark-cerulean-green (`dcg`)

### dark-grey-yellow (`dgy`)

### dark-indigo-red (`dir`)

### davys-maximum-green (`dmg`)

### deep-jungle-princeton (`djp`)

### dim-palatinate-blue (`dpb`)

### electric-purple-indigo (`epi`)
> **Observação**
>
> Amostra deste tema ainda não disponível.
### eminence-cornflower-blue (`ecb`)

### english-vermillion-arsenic (`eva`)

### folly-russian-violet (`frv`)

### french-blue-pink (`fbp`)

### generic-viridian-blue (`gvb`)

### green-dark-coral (`gdc`)

### gun-metal-camel (`gmc`)
> **Observação**
>
> Amostra deste tema ainda não disponível.
### independence-green (`idg`)

### international-orange-apricot (`ioa`)

### international-orange-green (`iog`)

### metallic-seaweed-emerald (`mse`)

### onyx-carrot-orange (`oco`)

### onyx-satin-gold (`osg`)

### oregon-blue-cadet (`obc`)
> **Observação**
>
> Amostra deste tema ainda não disponível.
### oxford-blue-green (`obg`)

### persian-plum-sand (`pps`)

### prussian-blue-green (`pbg`)

### queen-blue-mint (`qbm`)

### sea-green-yellow (`sgy`)

### silver-eerie-black (`seb`)

### space-cadet-yellow (`scy`)

### teal-blue-gold (`tbg`)

### viridian-green-yellow (`vgy`)

### vivid-sky-blue (`vsb`)

### yale-blue-yellow (`yby`)

### zomp-ut-orange (`zuo`)

# [Home page e footer](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/home-e-footer)
Existem duas formas de configurar a home page:
* **Configuração simples**: personaliza apenas a área de destaque, mantendo o restante da página.
* **Configuração avançada**: substitui a home inteira por uma página estática.
## Configuração simples
Permite personalizar a área demarcada abaixo:

É preciso apenas fornecer uma imagem de como gostaria que ficasse ou então o HTML/CSS correspondente.
Caso deseje criar seu próprio HTML e CSS, as seguintes regras devem ser observadas:
* Criar um arquivo HTML para cada linguagem disponível, no formato `home-.html`. Exemplo:
`home-pt.html`, `home-es.html` e `home-en.html`.
* Os arquivos HTML não podem conter tags `script` nem *inline styles*.
* Os arquivos HTML poderão usar classes definidas na biblioteca *Bootstrap* versão `4.3.1` referentes aos
seguintes módulos:
* [Grid](https://getbootstrap.com/docs/4.3/layout/grid)
* [Utilities for layout](https://getbootstrap.com/docs/4.3/layout/utilities-for-layout)
* A aplicação segue o padrão [Material Design](https://material.io) e, portanto, classes definidas na
biblioteca [Angular Material UI](https://material.angular.io) também podem ser utilizadas.
* Caso seja necessário personalizar o CSS, deve ser criado um arquivo denominado `main.css`.
* Os arquivos HTML e CSS devem ser colocados na pasta `assets` do *Blob Storage* configurado. Ao fazer a
atualização, a aplicação precisa ser reiniciada.
## Configuração avançada (página estática)
Permite personalizar toda a home page, de maneira que a página passa a ser estática. Exemplo:

Para isso, é preciso fornecer um arquivo HTML chamado `index.html` acompanhado de uma pasta denominada
`theme-assets` com todos os recursos de que o arquivo depender: imagens, CSS e JS. **Subpastas não são
suportadas**, isto é, os arquivos devem ser adicionados na raiz da pasta `theme-assets`:

Todos os arquivos devem ser colocados na pasta `assets` do *Blob Storage* configurado. Ao fazer a
atualização, a aplicação precisa ser reiniciada.
> **Aviso**
>
> Ao usar esta abordagem, recomenda-se que não sejam feitas muitas alterações no cabeçalho da página, tendo
> em vista que existem páginas da área externa da aplicação cujo cabeçalho não irá seguir esse modelo, como
> por exemplo a tela de assinatura externa e a de validação de documentos.
## Footer
Permite personalizar o footer da home page, caso a configuração de home page seja a simples:

É preciso apenas fornecer uma imagem de como gostaria que ficasse ou então o HTML/CSS correspondente.
## Veja também
* [Identidade visual](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/identidade-visual)
* [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings)
# [E-mails e notificações](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/emails)
Há duas coisas diferentes que podem ser personalizadas nos e-mails do Signer:
1. **A aparência dos e-mails de cada organização**, com logo, cores e alguns textos fixos. Configurado
pelo administrador da organização, na própria aplicação.
2. **Os textos dos e-mails e notificações**, sobrescrevendo qualquer texto, em qualquer idioma.
Configurado pelo administrador do sistema.
> **Dica**
>
> Para configurar o **servidor de envio** (SMTP próprio), veja
> [Servidor de e-mail (SMTP)](https://docs.lacunasoftware.com/articles/signer/on-premises/email-smtp), na seção de Integração.
## Personalização por organização
Cada organização tem suas próprias configurações de e-mail, aplicadas às notificações dos documentos
daquela organização:
| Configuração | O que faz |
|---|---|
| `Logo` | Logo exibida no topo dos e-mails. Se não for enviada, usa a `light-logo` da instância |
| `ThemeColor` | Cor principal do e-mail |
| `ThemeLinkColor` | Cor dos links |
| `ButtonBackgroundColor` | Cor de fundo dos botões |
| `ButtonTextColor` | Cor do texto dos botões |
| `SupportEmailAddress` | E-mail de suporte exibido ao destinatário |
| `EmailSubjectPrefix` | Prefixo adicionado ao assunto de todos os e-mails da organização |
| `EmailExtraContent` | Conteúdo extra incluído no corpo dos e-mails |
Pela API, essas configurações ficam no objeto `mail` das configurações da organização:
```text
GET /api/organizations/{orgId}/settings
PUT /api/organizations/{orgId}/settings
```
```json
{
"mail": {
"themeColor": "#1B4965",
"themeLinkColor": "#5FA8D3",
"buttonBackgroundColor": "#1B4965",
"buttonTextColor": "#FFFFFF",
"supportEmailAddress": "suporte@suaempresa.com.br",
"emailSubjectPrefix": "[Sua Empresa]",
"emailExtraContent": "Em caso de dúvidas, fale com o seu gerente de contas."
}
}
```
A logo é enviada como um upload, conforme
[Primeiros passos](https://docs.lacunasoftware.com/articles/signer/get-started#passo-2-envie-o-arquivo), e informada no campo
`mail.logo`.
> **Dica**
>
> Esta é a personalização que a maior parte dos clientes procura, e **não depende da equipe de implantação**:
> o administrador da organização faz sozinho, pela tela de configurações da organização.
## Textos dos e-mails e notificações
Qualquer texto de e-mail ou notificação pode ser sobrescrito pela seção de configuração
**`CustomResources`**, sem alterar o produto. É o mecanismo usado, por exemplo, para trocar "Assinar
documento" por um termo próprio da sua operação.
A configuração é organizada em **localizadores** (`Localizers`), cada um correspondendo a um conjunto de
textos:
| Localizador | O que contém |
|---|---|
| `MailFormatter` | Textos do corpo dos e-mails: saudações, botões, instruções, textos de fatura |
| `NotificationRepository` | Títulos e descrições das notificações dentro da aplicação |
| `NotificationService` | Assuntos dos e-mails e mensagens de eventos do documento |
Para cada chave é possível definir o valor por idioma (`pt`, `en`, `es`) e um valor `Default`, usado quando
não houver valor para o idioma solicitado:
```ini
CustomResources__Localizers__MailFormatter__Resources__Sign__pt=Assinar contrato
CustomResources__Localizers__MailFormatter__Resources__Sign__Default=Sign contract
```
Em formato JSON (`appsettings.json`):
```json
{
"CustomResources": {
"Localizers": {
"MailFormatter": {
"Resources": {
"Sign": {
"pt": "Assinar contrato",
"en": "Sign contract",
"es": "Firmar contrato",
"Default": "Sign contract"
}
}
}
}
}
}
```
> **Observação**
>
> A seção `CustomResources` está disponível desde a versão **1.37.0**. Veja também a referência em
> [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings).
### Chaves disponíveis
As chaves abaixo são as que podem ser sobrescritas em cada localizador.
**MailFormatter (114 chaves)**
* `AcceptInvite`
* `AddedActions`
* `AddedObserverText`
* `AddedObservers`
* `AdministrationArea`
* `ApprovalCompletedText`
* `Approve`
* `Approver`
* `AuthenticationCode`
* `BankSlipAttachmentFileName`
* `BatchTextApprove`
* `BatchTextDelete`
* `BatchTextMove`
* `BatchTextRefuse`
* `BatchTextSign`
* `ButtonViewAllDocuments`
* `CancelReason`
* `ClosedInvoiceOrganizationText`
* `ClosedInvoicePersonalText`
* `ClosedInvoiceSubject`
* `CompletedFastSignatureText`
* `ContactMessage`
* `CreatedInvoiceOrganizationText`
* `CreatedInvoicePersonalText`
* `CreatedInvoiceSubject`
* `DeletedActions`
* `DeletedDocumentText`
* `DeletedObservers`
* `DocumentAttachmentDeletedText`
* `DocumentAttachmentsAddedText`
* `DocumentCanceledText`
* `DocumentInfoUpdatedText`
* `DocumentPausedText`
* `DocumentRefusalText`
* `DocumentResumedText`
* `DocumentVersionAddedText`
* `Documents`
* `DocumentsConcludedIntroPlural`
* `DocumentsDeletedIntroPlural`
* `DownloadReport`
* `EditedFlowIntro`
* `ExpiredDocumentText`
* `ExtraLinkMessage`
* `FailedInvoiceCharge`
* `FailedInvoiceChargeOrganizationText`
* `FailedInvoiceChargePersonalText`
* `FailoverMessage`
* `FirstAccess`
* `FirstDocument`
* `GrantAccess`
* `Greetings`
* `GreetingsUser`
* `HelloUser`
* `InfoCurrentTags`
* `InfoDeletedTags`
* `InfoDescription`
* `InfoExpirationDate`
* `InfoFromTo`
* `InfoName`
* `InfoRemoved`
* `Instructions`
* `JoinOrganizationInvite`
* `NewTermsOfUseText`
* `ObserversIntro`
* `ParticipatedDocumentConcluded`
* `PauseReason`
* `PendingActionIntro`
* `PendingActionIntroPlural`
* `PendingActionIntroPluralMobile`
* `PendingApprovalText`
* `PendingApprovalTextMobile`
* `PendingDocumentsText`
* `PendingSignatureText`
* `PendingSignatureTextMobile`
* `PendingSignatureTextSMS`
* `PixPaymentText`
* `PixPaymentTitle`
* `ReceiptIssuedOrganizationText`
* `ReceiptIssuedPersonalText`
* `ReceiptIssuedSubject`
* `RefusalReason`
* `ReminderInvoice`
* `ReminderInvoiceOrganizationText`
* `ReminderInvoicePersonalText`
* `ReportAvailable`
* `ReportError`
* `RequestAccessUserInfo`
* `Rule`
* `SetPassword`
* `Sign`
* `SignIsEasy`
* `SignWithQrCode`
* `SignatureCompletedText`
* `SignatureConcludedText`
* `Signer`
* `Start`
* `SubjectAuthenticationCode`
* `SubjectReportAvailable`
* `SubjectReportError`
* `SuccessInvoiceCharge`
* `SuccessInvoiceChargeOrganizationText`
* `SuccessInvoiceChargePersonalText`
* `TermsOfUseUpdatedAdminText`
* `TryCompleteRegistration`
* `TryNow`
* `Unsubscribe`
* `UnsubscribeError`
* `UnsubscribeSuccess`
* `ViewDocument`
* `ViewDocuments`
* `ViewInvoice`
* `ViewTermsOfUse`
* `WelcomeTo`
* `WelcomeUser`
**NotificationRepository (22 chaves)**
* `AccessRequest`
* `AddedAsObserver`
* `CreateBatchApproved`
* `CreateBatchDeleted`
* `CreateBatchMoved`
* `CreateBatchRefused`
* `CreateBatchSigned`
* `DocumentCompleted`
* `DocumentInformationUpdated`
* `DocumentSigned`
* `DocumentsCompleted`
* `DocumentsDeleted`
* `ElectronicSignatureAuthCode`
* `ObserversDocuments`
* `PendingAction`
* `PendingActionMobile`
* `PendingApproval`
* `PendingApprovalMobile`
* `PendingSignature`
* `PendingSignatureMobile`
* `TermsOfUseUpdatedAdmin`
* `UpdateTermsOfUse`
**NotificationService (31 chaves)**
* `CreateBatchDeleted`
* `DocumentApproved`
* `DocumentAttachmentDeleted`
* `DocumentAttachmentsAdded`
* `DocumentCanceled`
* `DocumentDeleted`
* `DocumentExpired`
* `DocumentFlowEdited`
* `DocumentHasNoPendingFlowAction`
* `DocumentIsPaused`
* `DocumentPaused`
* `DocumentRefused`
* `DocumentResumed`
* `DocumentSigned`
* `DocumentVersionAdded`
* `SubjectDocumentCanceled`
* `SubjectDocumentConcluded`
* `SubjectDocumentDeleted`
* `SubjectDocumentExpired`
* `SubjectDocumentPaused`
* `SubjectDocumentRefused`
* `SubjectDocumentResumed`
* `SubjectDocumentVersionAdded`
* `SubjectElectronicSignatureAuthenticationCode`
* `SubjectFastSignatureCompleted`
* `SubjectFlowActionCompleted`
* `SubjectFlowEdited`
* `SubjectMultiplePendingActions`
* `SubjectObserverAdded`
* `SubjectPendingApproval`
* `SubjectPendingSignature`
## Veja também
* [Servidor de e-mail (SMTP)](https://docs.lacunasoftware.com/articles/signer/on-premises/email-smtp) trata da configuração do servidor de envio.
* [Identidade visual](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/identidade-visual) traz a logo padrão usada nos e-mails da instância.
* [Documentos gerados](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/documentos) cobre a personalização por organização da versão para impressão.
* [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings) é a referência completa das seções de configuração.
# [Documentos gerados](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/documentos)
Além da aplicação, os **arquivos que o Signer gera** também podem ser personalizados: a versão para
impressão, o carimbo de validação e a representação visual da assinatura.
Todas as configurações desta página são **por organização**: cada administrador de organização configura a
sua, sem depender da equipe de implantação. Pela API, todas ficam em:
```text
GET /api/organizations/{orgId}/settings
PUT /api/organizations/{orgId}/settings
```
## Versão para impressão
A versão para impressão (`PrinterFriendly`) é o PDF com o manifesto de assinaturas, baixado com
`type=PrinterFriendlyVersion`. É possível trocar a logo e ajustar o posicionamento dela e do resumo de
assinaturas:
| Configuração | O que faz |
|---|---|
| `Logo` | Logo exibida no documento. Sem ela, usa a `stamp-logo` da instância |
| `ShowLogo` | Exibe ou oculta a logo |
| `LogoWidthCentimeters` / `LogoHeightCentimeters` | Dimensões da logo, em centímetros |
| `LogoMarginRightCentimeters` / `LogoMarginBottomCentimeters` | Margens da logo, em centímetros |
| `ShowSummaryRight` | Posiciona o resumo de assinaturas à direita |
| `SummaryHeightCentimeters` | Altura do resumo, em centímetros |
| `SummaryLeadingCentimeters` / `SummaryTrailingCentimeters` | Espaçamento antes e depois do resumo |
| `SummaryMarginToPageCentimeters` | Margem do resumo em relação à página |
```json
{
"printerFriendly": {
"showLogo": true,
"logoWidthCentimeters": 3.5,
"logoHeightCentimeters": 1.2,
"logoMarginRightCentimeters": 1,
"logoMarginBottomCentimeters": 0.5,
"showSummaryRight": false
}
}
```
## Carimbo de validação
O carimbo de validação (`ValidationStamp`) usa exatamente as mesmas configurações da versão para
impressão (logo, dimensões e margens), aplicadas ao carimbo aposto no documento. Sem logo própria, usa a
`val-stamp-logo` da instância.
```json
{
"validationStamp": {
"showLogo": true,
"logoWidthCentimeters": 2,
"logoHeightCentimeters": 2
}
}
```
## Representação visual da assinatura
A imagem exibida na representação visual da assinatura dentro do PDF (`SignatureImage`):
| Configuração | O que faz |
|---|---|
| `VisualRepresentationEnabled` | Habilita a representação visual nas assinaturas da organização |
| `Image` | Imagem usada na representação visual |
| `AlignTextOnMiddle` | Alinha o texto verticalmente ao centro da representação |
```json
{
"signatureImage": {
"visualRepresentationEnabled": true,
"alignTextOnMiddle": true
}
}
```
> **Observação**
>
> As logos e imagens desta página são enviadas como upload e informadas no campo `logo` (ou `image`) da
> respectiva seção. Veja o passo de upload em
> [Primeiros passos](https://docs.lacunasoftware.com/articles/signer/get-started#passo-2-envie-o-arquivo).
## Veja também
* [Identidade visual](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/identidade-visual) traz as logos padrão da instância (`stamp-logo` e
`val-stamp-logo`).
* [E-mails e notificações](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/emails) cobre a personalização dos e-mails por organização.
* [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings) documenta as seções `PrinterFriendly`, `ValidationStamp` e
`VisualRepresentation` no nível da instância.
# [Domínio próprio](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/dominio)
Para servir a sua instância em um domínio seu, basta adicionar um registro DNS do tipo **CNAME** apontando
para o endereço da instância que será fornecido, e notificar a equipe de implantação para que façam os
ajustes necessários (certificado e configuração do site).
## Veja também
* [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings)
* [Envio de SMS](https://docs.lacunasoftware.com/articles/signer/on-premises/sms)
# [TrustArbitrator](https://docs.lacunasoftware.com/articles/signer/on-premises/trust-arbitrator)
O TrustArbitrator define quais certificados digitais o Signer aceita. Ao assinar ou validar uma assinatura digital, o Signer monta a cadeia do certificado. A cadeia é a lista de certificados que liga o certificado do signatário a uma AC raiz (autoridade certificadora de nível mais alto). Depois, o Signer pergunta ao TrustArbitrator se essa raiz é confiável. Se o TrustArbitrator não aceita a raiz, o Signer recusa a assinatura.
Por padrão, o Signer aceita apenas certificados da ICP-Brasil. Esta página explica como mudar essa configuração. Por exemplo, você pode aceitar uma AC raiz própria, o e-Notariado ou certificados de outros países.
## Onde o TrustArbitrator é usado
O Signer tem um TrustArbitrator padrão da instância e um TrustArbitrator para cada tipo de assinatura:
| Onde | Configuração | Padrão |
|---|---|---|
| Padrão da instância | Seção `TrustArbitrator` | ICP-Brasil |
| Tipo de assinatura Simples | Seção `SignatureTypes:Simple:TrustArbitrator` | ICP-Brasil |
| Tipo de assinatura Qualificada | Seção `SignatureTypes:Qualified:TrustArbitrator` | ICP-Brasil |
| Tipo de assinatura Avançada | Contexto de segurança, na área de administração | Definido em cada contexto |
Se a seção não existir, ou se `Type` estiver vazio, o TrustArbitrator aceita apenas a ICP-Brasil.
O Signer usa o TrustArbitrator padrão nestes casos:
* Na assinatura e na validação digital, se `SignatureTypes` está desabilitado ou se a operação não informa o tipo de assinatura.
* Na autenticação por certificado digital nos serviços de assinatura em nuvem CSC.
* No manifesto simplificado, para decidir se o texto sobre a ICP-Brasil aparece.
As seções `SignatureTypes:Simple:TrustArbitrator` e `SignatureTypes:Qualified:TrustArbitrator` só têm efeito se `SignatureTypes:Enabled` é `true`. As [configurações de Signature Types](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-signature-types-v1680) descrevem os parâmetros de `SignatureTypes`.
> **Observação: Assinatura Avançada**
>
> O tipo Avançada não usa o arquivo de configuração. Um contexto de segurança agrupa as regras de assinatura Avançada, e cada contexto tem o próprio TrustArbitrator. O administrador da instância define esse TrustArbitrator na área de administração. Ele escolhe as PKIs padrão e envia as raízes confiáveis do contexto.
## Formato do JSON do TrustArbitrator
Um JSON descreve o TrustArbitrator. As duas formas de configuração desta página usam o mesmo JSON:
```json
{
"Version": "2019-05-09",
"StandardPkis": [ "Brazil" ],
"TrustedRoots": []
}
```
| Propriedade | Obrigatória | Descrição |
|---|---|---|
| `Version` | Sim | Versão do formato: `2019-05-09` ou `2022-07-21`. Qualquer outro valor gera erro. |
| `StandardPkis` | Não | Lista de PKIs padrão aceitas. Veja os valores em [Valores de `StandardPkis`](https://docs.lacunasoftware.com/articles/signer/on-premises/trust-arbitrator#valores-de-standardpkis). |
| `TrustedRoots` | Não | Lista de certificados de AC raiz aceitos, além das PKIs padrão. Cada item é o certificado em Base64 (DER) ou em formato PEM. |
| `IntermediateCAs` | Não | Lista de certificados de ACs intermediárias, no mesmo formato de `TrustedRoots`. Só existe na versão `2022-07-21`. |
> **Observação: ACs intermediárias**
>
> Uma AC intermediária fica entre a AC raiz e o certificado do signatário. Use `IntermediateCAs` se os certificados dos signatários não trazem as informações para o Signer baixar essas ACs. Os certificados de `IntermediateCAs` não são confiáveis sozinhos. A cadeia ainda precisa terminar em uma raiz aceita.
### Valores de `StandardPkis`
Uma PKI (infraestrutura de chaves públicas) é o conjunto de ACs de um país ou setor. A propriedade `StandardPkis` aceita os valores abaixo:
| Valor | Certificados aceitos |
|---|---|
| `Brazil` | ICP-Brasil |
| `Italy` | PKI da Itália |
| `Peru` | PKI do Peru |
| `System` | Raízes confiáveis do sistema operacional do servidor |
| `ENotariado` | e-Notariado (Colégio Notarial do Brasil) |
| `RegistroCivil` | Registro Civil do Brasil (IdRC) |
Se o TrustArbitrator tem mais de uma PKI ou raiz, basta que uma delas aceite a raiz do certificado.
## Onde guardar o JSON
A propriedade `Type` define onde o Signer busca o JSON do TrustArbitrator. Escolha a forma pelo conteúdo do JSON:
| `Type` | Onde fica o JSON | Use se |
|---|---|---|
| `AppSetting` | Na propriedade `Content`, como texto | Você usa apenas PKIs padrão. |
| `BlobStorage` | Em um arquivo no Blob Storage | O TrustArbitrator tem raízes ou ACs intermediárias próprias. |
Com raízes próprias, o texto de `Content` fica longo e difícil de manter. Por isso, use `BlobStorage` nesse caso.
> **Observação: Variáveis de ambiente**
>
> Em instalações com Docker ou Azure App Services, você pode definir as configurações por variáveis de ambiente. Separe cada nível com `__` (dois sublinhados). Os exemplos desta página mostram as duas formas. Depois de alterar `appsettings.json` ou as variáveis de ambiente, reinicie a aplicação.
### Opção 1: JSON nas configurações (`AppSetting`)
Nesta opção, o JSON do TrustArbitrator fica na propriedade `Content`, como texto em uma única linha. É a forma mais simples. O exemplo abaixo aceita ICP-Brasil e e-Notariado:
**appsettings.json**
```json title="appsettings.json"
{
"TrustArbitrator": {
"Type": "AppSetting",
"Content": "{ \"Version\": \"2019-05-09\", \"StandardPkis\": [ \"Brazil\", \"ENotariado\" ] }"
}
}
```
**Variáveis de ambiente**
```text
TrustArbitrator__Type=AppSetting
TrustArbitrator__Content={ "Version": "2019-05-09", "StandardPkis": [ "Brazil", "ENotariado" ] }
```
| Propriedade | Obrigatória | Descrição |
|---|---|---|
| `Type` | Sim | `AppSetting` |
| `Content` | Sim | O JSON do TrustArbitrator, como texto |
> **Aviso**
>
> No `appsettings.json`, escape todas as aspas duplas de `Content` com `\"`. `Content` é um texto dentro de outro JSON. Se `Content` estiver vazio ou tiver um JSON inválido, as operações que usam o TrustArbitrator falham.
### Opção 2: arquivo no Blob Storage (`BlobStorage`)
Nesta opção, o JSON do TrustArbitrator fica em um arquivo no Blob Storage da instância. Antes de começar, verifique se o Blob Storage está configurado. Veja [Configuração do Blob Storage](https://docs.lacunasoftware.com/articles/signer/on-premises/blob-storage).
1. Crie um arquivo `.json` com o conteúdo do TrustArbitrator. Por exemplo:
```json title="meu-trustarbitrator.json"
{
"Version": "2019-05-09",
"StandardPkis": [ "Brazil" ],
"TrustedRoots": [
"MIIGBjCCA+6gAwIBAgIBATANBgkqhkiG9w0BAQ0FADCBkzELMAkGA1UEBhMCQlIx..."
]
}
```
1. Envie o arquivo para a pasta `trustarbitrators` do Blob Storage. O local depende do tipo de Blob Storage:
| Tipo de Blob Storage | Local do arquivo |
|---|---|
| Sistema de arquivos | `/trustarbitrators/meu-trustarbitrator.json` |
| Azure | Contêiner configurado, com o prefixo `trustarbitrators/` |
1. Aponte o Signer para o arquivo com a configuração abaixo.
**appsettings.json**
```json title="appsettings.json"
{
"TrustArbitrator": {
"Type": "BlobStorage",
"BlobName": "meu-trustarbitrator.json"
}
}
```
**Variáveis de ambiente**
```text
TrustArbitrator__Type=BlobStorage
TrustArbitrator__BlobName=meu-trustarbitrator.json
```
| Propriedade | Obrigatória | Descrição |
|---|---|---|
| `Type` | Sim | `BlobStorage` |
| `BlobName` | Sim | Nome do arquivo no Blob Storage |
| `BlobFolderName` | Não | Pasta do arquivo no Blob Storage. O padrão é `trustarbitrators`. Mude só se o arquivo estiver em outra pasta. |
> **Aviso**
>
> Não nomeie o arquivo do TrustArbitrator padrão com um GUID. A pasta `trustarbitrators` também guarda os TrustArbitrators dos contextos de segurança. O Signer nomeia esses arquivos com GUIDs e os apaga quando o contexto muda ou é excluído.
O Signer lê o arquivo a cada operação. Por isso, uma alteração no arquivo vale para as próximas assinaturas e validações. Você não precisa reiniciar a aplicação.
## TrustArbitrators dos tipos Simples e Qualificada
As seções `SignatureTypes:Simple:TrustArbitrator` e `SignatureTypes:Qualified:TrustArbitrator` aceitam as mesmas propriedades da seção `TrustArbitrator`: `Type`, `Content`, `BlobName` e `BlobFolderName`. Se a seção não existir, o tipo de assinatura aceita apenas a ICP-Brasil.
No exemplo abaixo, o tipo Qualificada aceita ICP-Brasil e e-Notariado. O tipo Simples usa o TrustArbitrator de um arquivo no Blob Storage:
**appsettings.json**
```json title="appsettings.json"
{
"SignatureTypes": {
"Enabled": true,
"Simple": {
"TrustArbitrator": {
"Type": "BlobStorage",
"BlobName": "simples.json"
}
},
"Qualified": {
"TrustArbitrator": {
"Type": "AppSetting",
"Content": "{ \"Version\": \"2019-05-09\", \"StandardPkis\": [ \"Brazil\", \"ENotariado\" ] }"
}
}
}
}
```
**Variáveis de ambiente**
```text
SignatureTypes__Enabled=true
SignatureTypes__Simple__TrustArbitrator__Type=BlobStorage
SignatureTypes__Simple__TrustArbitrator__BlobName=simples.json
SignatureTypes__Qualified__TrustArbitrator__Type=AppSetting
SignatureTypes__Qualified__TrustArbitrator__Content={ "Version": "2019-05-09", "StandardPkis": [ "Brazil", "ENotariado" ] }
```
## Solução de problemas
O Signer registra no log as mensagens abaixo quando a configuração do TrustArbitrator tem erro:
| Mensagem no log | Causa provável |
|---|---|
| `Invalid trust arbitrator JSON` | O JSON está malformado. Verifique se as aspas de `Content` têm escape e se o conteúdo do arquivo é um JSON válido. |
| `'version' property not found` | O JSON não tem a propriedade `Version`. |
| `Unrecognized version` | `Version` tem um valor diferente de `2019-05-09` e `2022-07-21`. |
| `The content for the trust arbitrator JSON must be set...` | `Type` é `AppSetting`, mas `Content` está vazio. |
| `The blobName for the trust arbitrator JSON must be configured` | `Type` é `BlobStorage`, mas `BlobName` está vazio. |
Se o Signer recusar uma assinatura porque a raiz do certificado não é confiável, faça estas verificações:
1. Veja em [Onde o TrustArbitrator é usado](https://docs.lacunasoftware.com/articles/signer/on-premises/trust-arbitrator#onde-o-trustarbitrator-é-usado) qual TrustArbitrator a operação usa.
1. Verifique se a PKI do certificado está em `StandardPkis` desse TrustArbitrator.
1. Se a PKI não está na lista, verifique se a raiz do certificado está em `TrustedRoots`.
## Veja também
* [Configurações do Signer](https://docs.lacunasoftware.com/articles/signer/on-premises/settings#configurações-trust-arbitrator)
* [Configuração do Blob Storage](https://docs.lacunasoftware.com/articles/signer/on-premises/blob-storage)
# [Atualizando o Signer da versão 1.x para 2.x](https://docs.lacunasoftware.com/articles/signer/on-premises/update-20)
Antes de atualizar sua instância [on-premises](https://docs.lacunasoftware.com/articles/signer/on-premises) da versão 1.x para a 2.x, é preciso seguir alguns passos adicionais.
## 1. Instalar o ASP.NET Core Runtime 8.0
Por favor verifique a seção *Instale o ASP.NET Core Runtime 8.0* do procedimento de instalação da plataforma que está utilizando:
* Windows: Instale [NET 8.0 Hosting Bundle](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-aspnetcore-8.0.15-windows-hosting-bundle-installer)
* Linux
* [Ubuntu Server](https://docs.lacunasoftware.com/articles/signer/on-premises/linux/install-ubuntu#install-aspnet-core)
> **Dica**
>
> Não é necessário desinstalar versões prévias do ASP.NET Core Runtime, pois múltiplas versões podem coexistir no mesmo ambiente sem problemas.
> **Observação**
>
> No Docker, esse passo não é necessário pois as versões 2.x da imagem já vêm com o ASP.NET Core Runtime 8.0 embutido.
## 2. Defina um AppDiscriminator explícito
Verifique se o seu arquivo de configuração contém a configuração chamada `AppDiscriminator` na seção `General` (ou `General__AppDiscriminator` se estiver usando variáveis de ambiente).
Se existir a configuração e estiver preenchida, pule para o próximo passo.
Se não existir a configuração ou se o valor estiver vazio, verifique os logs da aplicação e procure pela entrada mais recente com o modelo abaixo:
```text
Application starting: Lacuna Signer (version: *, spaVersion: *, appDiscriminator: 'YOUR_APP_DISCRIMINATOR')
^^^^^^^^^^^^^^^^^^^^^^
```
Copie o valor do campo *appDiscriminator* **sem as aspas simples que o cercam** (no exemplo acima o valor correto seria `YOUR_APP_DISCRIMINATOR`,
não `'YOUR_APP_DISCRIMINATOR'`) e adicione a configuração `AppDiscriminator` com esse valor na seção `General` do seu arquivo de configuração.
Exemplo (*.ini* ou arquivo *.conf*):
```ini
[General]
AppDiscriminator=YOUR_APP_DISCRIMINATOR
```
Exemplo (environment variables):
```sh
General__AppDiscriminator=YOUR_APP_DISCRIMINATOR
```
Em seguida, reinicie a aplicação e verifique os logs de novo. Um novo log do tipo "application starting" deve aparecer e nenhuma mudança deve ser registrada no campo *appDiscriminator*.
## 3. Mudanças do Serilog
Alguns parâmetros do Serilog mudaram porque a biblioteca foi atualizada. Confira a seção `Serilog` dos seus app settings e faça mudanças de acordo com os direcionamentos abaixo:
* Windows:
* Se você tiver o parâmetro `RollingFile` especificado, troque-o por `File` e a propriedade `pathFormat` por `path`. Para manter o comportamento de *rolling* adicione `"rollingInterval": "Day"`.
* Azure:
* Se você estiver hospedando o Signer no Azure (e tiver a configuração `ASPNETCORE_ENVIRONMENT` definida para `Azure`), configurações padrões de Serilog foram adicionadas, então agora só é necessário definir a configuração `Serilog__WriteTo__0__Args__connectionString` para direcionar os logs para uma table de um Storage Account.
* Se você tiver a configuração `Serilog__WriteTo__0__Name` definida com o valor `AzureTableStorageWithProperties`, remova-a ou substitua o valor por `AzureTableStorage`.
## 4. Mudanças de banco de dados
Foi alterada a versão mínima exigida do SQL Server para o SQL Server 2016 (13.x). Para reverter a compatibilidade para o SQL Server 2014,
adicione a configuração `SqlServer__CompatibilityLevel` com valor `120`.
## 5. Update Signer
Após seguir os passos acima, proceda com as instruções de atualização normais.
## Solucionando erros de certificado no SQL Server
Diferentemente do .NET 6, o novo padrão para o .NET 8 é que o certificado do servidor SQL Server seja validado. Isso pode ocasionar uma falha ao iniciar a aplicação com o seguinte
erro sendo apresentado nos logs:
> Microsoft.Data.SqlClient.SqlException: 'A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.)'
Nesse caso, verifique que seu SQL Server está configurado com um certificado TLS confiável e que ele não está expirado.
Se você preferir contornar a verificação do certificado do servidor SQL Server, adicione `;TrustServerCertificate=True` à connection string do banco de dados no arquivo de configuração.
## Solucionando erros de timeout do banco de dados após o update
Se você enfrentar erros durante o primeiro início da aplicação após a atualização e o log indicar que ocorreu um timeout ao aplicar as migrações de banco de dados, você deverá
executar o utilitário de linha de comando para iniciar as migrações de maneira manual usando um parâmetro de timeout com valor maior que o padrão:
1. Pare a aplicação.
1. Navegue até a pasta aonde se encontram os binários da aplicação.
1. Execute o comando:
```sh
dotnet Lacuna.Signer.Site.dll -- update-db AddFontSize --timeout 6000
```
1. Reinicie a aplicação.
## Azure App Services
Se sua instâcnia está hospeada no Azure App Services:
1. No portal do Azure, acesse as configurações do App Service.
1. Selecione **Configuration**, e depois selecione a aba **General settings**
1. Na seção *Stack settings*:
1. A configuração de *Stack* deve estar definida como **.NET**
1. A configuração *.NET version*, deve estar definida como **.NET 8 (LTS)**
> **Dica**
>
> A seção *Stack settings* mencionada acima só aparece para App Service Plan de plataforma Windows. Para planos baseados em plataforma Linux, nenhuma mudança é necessária.
# [Primeiros passos com a API do Signer](https://docs.lacunasoftware.com/articles/signer/get-started)
Este guia leva você **do zero ao primeiro documento assinado** usando a API do Signer. São seis passos e
leva cerca de 15 minutos. Ao final você terá criado um documento por API, coletado uma assinatura e
baixado o arquivo assinado.
A referência completa de todos os casos de uso está no [Guia de Integração](https://docs.lacunasoftware.com/articles/signer/integration-guide).
## Antes de começar
Você vai precisar de:
* Acesso a uma instância do Signer, seja a versão SaaS em [dropsigner.com](https://www.dropsigner.com/) ou
uma instância [*on premises*](https://docs.lacunasoftware.com/articles/signer/on-premises).
* Uma **organização**, já que chaves de API pertencem sempre a uma organização.
* Permissão para criar aplicações naquela organização. Sem ela, peça ao administrador da sua instância uma
chave de API da organização e pule para o [passo 2](https://docs.lacunasoftware.com/articles/signer/get-started#passo-2-envie-o-arquivo).
* Um arquivo PDF qualquer para testar.
> **Observação**
>
> Nos exemplos deste guia a URL base é `https://www.dropsigner.com`. Se você usa uma instância própria,
> troque pela URL da sua instância, por exemplo `https://signer.suaempresa.com.br`.
## Passo 1: Crie a aplicação e a chave de API
Na organização desejada, acesse **Aplicações** e crie uma aplicação:

Em seguida, gere a chave clicando no botão **Chaves**:



A chave tem o formato `nome-da-aplicacao|` e deve ser enviada no cabeçalho `X-Api-Key` de **todas**
as requisições:
```text
X-Api-Key: sua-aplicacao|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
> **Aviso**
>
> A chave é exibida **uma única vez**, no momento da criação. Guarde-a em um cofre de segredos, porque uma
> chave perdida não pode ser recuperada, apenas substituída por uma nova. O escopo da chave é restrito à
> organização em que foi criada: ela só enxerga e cria documentos daquela organização.
Confirme que a chave funciona listando os documentos da organização:
```bash
curl -i "https://www.dropsigner.com/api/documents" -H "X-Api-Key: sua-aplicacao|SUA_CHAVE"
```
Uma resposta `401` ou `403` indica chave inválida, revogada ou de outra organização.
## Passo 2: Envie o arquivo
O arquivo é enviado antes do documento e recebe um identificador de upload, que será usado no passo
seguinte. A forma mais simples é enviar os bytes em Base64:
```bash
curl -X POST "https://www.dropsigner.com/api/uploads/bytes" -H "X-Api-Key: sua-aplicacao|SUA_CHAVE" -H "Content-Type: application/json" -d "{\"bytes\": \"$(base64 -w0 contrato.pdf)\"}"
```
Resposta:
```json
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"size": 48213,
"digest": "9bT1kQ2s3d4F5g6H7j8K9l=="
}
```
O campo `id` é o **ID do upload**. O `digest` é o MD5 do arquivo recebido e serve para conferir a
integridade do que chegou ao servidor.
> **Dica**
>
> Para arquivos grandes, prefira o upload `multipart/form-data` em `POST /api/uploads`, que evita o custo de
> transportar o arquivo em Base64.
## Passo 3: Crie o documento
Agora crie o documento a partir do upload, definindo o fluxo: quem participa e em que ordem.
```bash
curl -X POST "https://www.dropsigner.com/api/documents" \
-H "X-Api-Key: sua-aplicacao|SUA_CHAVE" \
-H "Content-Type: application/json" \
--data-binary @documento.json
```
Conteúdo de `documento.json`:
```json
{
"files": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"displayName": "Contrato de prestação de serviços",
"name": "contrato.pdf",
"contentType": "application/pdf"
}
],
"flowActions": [
{
"type": "Signer",
"step": 1,
"user": {
"name": "John Wick",
"identifier": "81976153069",
"email": "john.wick@mailinator.com"
},
"allowElectronicSignature": true
}
]
}
```
A resposta traz um item por arquivo enviado, relacionando o upload ao documento criado:
```json
[
{
"uploadId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"documentId": "b12cb1b2-5d6e-40b2-a050-097d068c4c11"
}
]
```
Entendendo os campos principais:
| Campo | Para que serve |
|---|---|
| `files[].id` | ID devolvido pelo upload no passo anterior |
| `files[].displayName` | Nome do documento como ele aparece no Signer |
| `files[].name` / `files[].contentType` | Nome e *mime type* do arquivo original |
| `flowActions[].type` | `Signer`, `Approver`, `SignRule` ou `ApproverRule` |
| `flowActions[].step` | Ordem no fluxo. Participantes no mesmo `step` agem em paralelo, e um `step` maior só é notificado quando o anterior termina |
| `flowActions[].user` | Identificação do participante (`name`, `identifier`, `email`) |
| `flowActions[].allowElectronicSignature` | Permite assinatura eletrônica, sem certificado digital |
> **Dica**
>
> Você pode enviar vários arquivos na mesma chamada. Cada um vira um documento, todos com o mesmo fluxo.
> Para agrupar vários PDFs em um único documento, use `isEnvelope: true` junto com `envelopeName`.
Assim que o documento é criado, o Signer envia automaticamente o e-mail de notificação aos participantes
do primeiro passo. Para não notificar ninguém nesse momento, como acontece quando a assinatura vai ocorrer
dentro da sua própria aplicação, envie `disablePendingActionNotifications: true`.
## Passo 4: Assine o documento
**Pela notificação (padrão).** O participante recebe um e-mail com um link que abre a tela de assinatura.
Não é preciso ter conta nem se autenticar no Signer para assinar.
**Dentro da sua aplicação.** Peça a URL de ação daquele participante e redirecione (ou embuta) o usuário:
```bash
curl -X POST "https://www.dropsigner.com/api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11/action-url" \
-H "X-Api-Key: sua-aplicacao|SUA_CHAVE" \
-H "Content-Type: application/json" \
-d "{\"identifier\": \"81976153069\", \"emailAddress\": \"john.wick@mailinator.com\"}"
```
Para esse caso, combine com `disablePendingActionNotifications: true` na criação. A página
[Assinatura Embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature) mostra como embutir a tela de assinatura na sua própria
página.
## Passo 5: Acompanhe o status
```bash
curl "https://www.dropsigner.com/api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11" -H "X-Api-Key: sua-aplicacao|SUA_CHAVE"
```
```json
{
"id": "b12cb1b2-5d6e-40b2-a050-097d068c4c11",
"name": "Contrato de prestação de serviços",
"status": "Concluded",
"flowActions": [
{
"id": "4bf61c68-eaf1-455f-b4a1-6141554f1dae",
"type": "Signer",
"status": "Completed",
"step": 1,
"user": { "name": "John Wick", "identifier": "81976153069" }
}
]
}
```
* O documento está finalizado quando `status` é `Concluded`.
* Um participante concluiu sua ação quando o `status` da `flowAction` dele é `Completed`.
> **Aviso**
>
> Não use esta chamada em *polling* para saber quando o documento ficou pronto. Em produção, cadastre um
> [Webhook](https://docs.lacunasoftware.com/articles/signer/webhooks) e deixe o Signer avisar a sua aplicação.
## Passo 6: Baixe o documento assinado
```bash
curl -L "https://www.dropsigner.com/api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11/content?type=Signatures" -H "X-Api-Key: sua-aplicacao|SUA_CHAVE" -o contrato-assinado.pdf
```
O parâmetro `type` escolhe qual versão baixar:
| `type` | O que é devolvido |
|---|---|
| `Signatures` | **O arquivo assinado**, ou seja, o PDF com as assinaturas embutidas (ou um `.p7s`, quando a assinatura é CAdES) |
| `Original` | O arquivo original, como foi enviado |
| `OriginalWithMarks` | O original com as marcas de assinatura carimbadas |
| `PrinterFriendlyVersion` | Versão para impressão, em PDF, com o manifesto de assinaturas |
| `SignaturesManifest` | Apenas o manifesto de assinaturas, em PDF |
| `SignatureMarks` | PDF com as representações visuais das assinaturas |
| `SigningTags` | PDF com as etiquetas de assinatura |
> **Observação**
>
> Documentos **cancelados, expirados ou recusados** só permitem baixar `Original` e `PrinterFriendlyVersion`;
> os demais tipos retornam erro. O download de documentos recusados pode ser liberado por configuração da
> instância.
Se precisar dos bytes em JSON em vez de um *download*, use `GET /api/documents/{id}/content-b64`, que
devolve `name`, `contentType` e os `bytes` em Base64.
## Pronto, e agora?
* [Guia de Integração](https://docs.lacunasoftware.com/articles/signer/integration-guide) cobre todos os casos de uso: lembretes, cancelamento, recusa,
nova versão, validação de assinaturas, listagens e pré-posicionamento de assinaturas.
* [Webhooks](https://docs.lacunasoftware.com/articles/signer/webhooks) mostra como receber os eventos do documento em vez de consultar o status.
* [Assinatura Embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature) traz a tela de assinatura para dentro da sua aplicação.
* [APIs de Administração](https://docs.lacunasoftware.com/articles/signer/on-premises/administration) permitem criar e gerenciar usuários e
organizações.
* As [bibliotecas cliente](https://docs.lacunasoftware.com/articles/signer/integration-guide#bibliotecas-cliente) para .NET, Java, Node.js e PHP chamam a
API por você, e [Respostas de erro](https://docs.lacunasoftware.com/articles/signer/integration-guide#respostas-de-erro) descreve o que a API devolve em caso
de falha.
* Exemplos completos em C#, Java, PHP e Node.js estão no
[SignerSamples](https://github.com/LacunaSoftware/SignerSamples).
**Demonstrações e exemplos**
- Demonstração: [Sign a document](https://try.lacunasoftware.com/pt/signer/sign-a-document)
- Sign a document: create it with one signer: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithOneSignerScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithOneSignerScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithOneSignerScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithOneSignerScenario.php)
- Create a document from a large file: [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateLargeDocumentScenario.php)
# [APIs de Administração](https://docs.lacunasoftware.com/articles/signer/on-premises/administration)
## Introdução
As APIs de administração podem ser utilizadas para criar, gerenciar e modificar usuários e organizações do Signer.
Para utilizar essas APIs é necessário criar uma **chave de API de administração**.
## Criação de chave de API de administração
Para gerá-la, crie uma aplicação selecionando a organização `Sys Admin`:


Em seguida, gere a chave selecionando o botão "Chaves":



## Opções de operações de administração
Escolha um dos casos de uso abaixo:
* [Listar usuários](https://docs.lacunasoftware.com/articles/signer/on-premises/administration#listar-usuários)
* [Atualizar dados de usuário](https://docs.lacunasoftware.com/articles/signer/on-premises/administration#atualizar-dados-de-usuário)
* [Listar organizações](https://docs.lacunasoftware.com/articles/signer/on-premises/administration#listar-organizações)
* [Criar uma nova organização](https://docs.lacunasoftware.com/articles/signer/on-premises/administration#criar-uma-nova-organização)
* [Listar usuários da organização](https://docs.lacunasoftware.com/articles/signer/on-premises/administration#listar-usuários-da-organização)
* [Criar ou atualizar usuários de uma organização](https://docs.lacunasoftware.com/articles/signer/on-premises/administration#criar-ou-atualizar-usuários-de-uma-organização)
* [Remover usuário de uma organização](https://docs.lacunasoftware.com/articles/signer/on-premises/administration#remover-usuário-de-uma-organização)
Essas e outras operações estão exemplificadas no arquivo [Postman](https://www.postman.com/) a seguir: [Signer ADM APIs](https://cdn.lacunasoftware.com/signer/docs/Signer%20ADM-5.postman_collection.json).
### Listar usuários
Para listar os usuários cadastrados no sistema, obtendo assim seus dados e permissões, utilize a chamada `GET /api/users`:
```json
GET /api/users?q=
{
"items": [
{
"identifier": "99999999999",
"phone": null,
"id": "446e14b9-a661-446a-bb52-66b6c9f1ade9",
"subject": "17bb43bd-1dac-46ce-95d2-62007bee2aea",
"name": "User One",
"emailAddress": "User.One@mailinator.com",
"parentId": null,
"rootRoles": [],
"parentRootRoles": null,
"subscriptions": [
{
"agentId": "446e14b9-a661-446a-bb52-66b6c9f1ade9",
"subscriptionId": "28dd8bc0-ef7a-44e4-b2f8-08da757b7cf1",
"roles": [
"Admin",
"Manager",
"Basic"
],
"parentRoles": null
}
]
},
{
"identifier": "11096802449",
"phone": "+55 61 99999-9999",
"id": "7b06a7ed-43fc-45df-96f8-cb29f4778e22",
"subject": "ee72e66e-c150-47c3-a8fd-d4d0c6e12ef5",
"name": "User Two",
"emailAddress": "user.two@mailinator.com",
"parentId": null,
"rootRoles": [],
"parentRootRoles": null,
"subscriptions": [
{
"agentId": "7b06a7ed-43fc-45df-96f8-cb29f4778e22",
"subscriptionId": "7289e60f-0c1f-4a3d-4b81-08da718c9ef3",
"roles": [
"Admin",
"Manager",
"Basic"
],
"parentRoles": null
}
]
}
],
"totalCount": 2,
"nextCursor": null
}
```
* q (query - `string`) : permite filtrar os usuários retornados por nome, CPF ou e-mail (opcional).
### Atualizar dados de usuário
Para atualizar os dados de um usuário, utilize a chamada `PUT /api/users/{subject}`:
```json
PUT /api/users/{subject}
{
"name": "User Name",
"identifier": "11096802449",
"emailAddress": "user@mailinator.com",
"phone": "+55 (61) 99999-9999"
}
```
* O parâmetro `{subject}` corresponde à informação de mesmo nome disponível quando recuperados os dados de um usuário conforme descrito em [Listar usuários](https://docs.lacunasoftware.com/articles/signer/on-premises/administration#listar-usuários).
### Listar organizações
Para listar as organizações cadastradas no sistema, utilize a chamada `GET /api/admin/organizations`:
```json
GET /api/admin/organizations?limit=1&offset=1&personal=true&q=user
{
"items": [
{
"id": "5aaa9032-1ad0-4c63-3c73-08d9d088eeac",
"name": null,
"identifier": null,
"owner": {
"id": "6f9d67cc-5cea-4672-b09e-ac04ed2e348f",
"name": "Novo User"
}
}
],
"totalCount": 17,
"nextCursor": null
}
```
* limit (`int`) : quantidade de itens por página (opcional).
* offset (`int`): número da página (opcional).
* q (query - `string`) : permite filtrar pelo nome da organização (opcional).
### Criar uma nova organização
Para criar uma organização, utilize a chamada `POST /api/admin/organizations`:
```json
POST /api/admin/organizations
{
"name": "newOrg",
"identifier": "85932577000194"
}
```
### Listar usuários da organização
Para listar os usuários de uma organização, utilize a chamada `GET /api/organizations/{id}/users`:
```json
GET /api/organizations/{id}/users?limit=1&offset=0&q=
{
"items": [
{
"id": "7b06a7ed-43fc-45df-96f8-cb29f4778e22",
"name": "User Name",
"email": "user.name@mailinator.com",
"identifier": "11096802449",
"role": "",
"parentId": null,
"accessProfile": {
"administrator": true,
"manager": true,
"parentRoles": null
},
"showNameWarning": false
}
],
"totalCount": 3,
"nextCursor": null
}
```
* `{id}`: ID da organização.
* limit (`int`): quantidade de itens por página (opcional).
* offset (`int`): número da página (opcional).
* q (query - `string`): permite filtrar pelo nome do usuário (opcional).
### Criar ou atualizar usuários de uma organização
Para criar ou atualizar usuários, utilize a chamada `POST /api/organizations/{id}/users`:
```json
POST /api/organizations/{id}/users
{
"accessProfile": {
"administrator": true,
"manager": true
},
"name": "User Name",
"identifier": "11096802449",
"email": "user@mailinator.com"
}
```
* `{id}`: ID da organização.
### Remover usuário de uma organização
Para remover um usuário da organização, utilize a chamada `DELETE /api/admin/organizations/{id}/users/{userID}`:
* `{id}`: ID da organização.
* `{userID}`: ID do usuário a ser removido.
# [Monitoramento de consumo](https://docs.lacunasoftware.com/articles/signer/on-premises/consumption-monitoring)
## Introdução
Para permitir o monitoramento do sistema, o Signer possui um conjunto de APIs que expõem informações e relatórios sobre
operações realizadas pelos usuários.
Para utilizar essas APIs é necessário criar uma **chave de API de monitoramento**.
## Criação de chave de API de monitoramento
Para gerá-la, crie uma aplicação selecionando a organização `Sys Admin` e o papel `Monitor`:


> **Aviso**
>
> O papel `Monitor` permitirá acesso apenas às APIs relevantes a atividades de monitoramento.
Em seguida, gere a chave selecionando o botão "Chaves":



## Opções de monitoramento
Escolha um dos casos de uso abaixo:
* [Gerar relatório de documentos](https://docs.lacunasoftware.com/articles/signer/on-premises/consumption-monitoring#gerar-relatorio-docs)
* [Obter totais de documentos](https://docs.lacunasoftware.com/articles/signer/on-premises/consumption-monitoring#obter-totais-docs)
### Gerar relatório de documentos
Para obter um relatório Excel de todos os documentos criados num determinado período, utilize a chamada `POST /api/reports/documents/date`:
```json
POST /api/reports/documents/date
{
"startDateUtc": "2023-01-01",
"endDateUtc": "2023-01-31"
}
```
* startDateUtc - `string`: data e hora (opcional) do início do período a ser considerado.
* endDateUtc - `string`: data e hora (opcional) do fim do período a ser considerado.
Como resposta é enviada um caminho para download do relatório gerado (é preciso concatenar o valor retornado com a URL base do site):
```json
{
"location": "/api/reports/f7044565-7cd4-4628-bb17-76eedbf9fe31?access_ticket=..."
}
```
### Obter totais de documentos
Para obter o total de documentos e de envelopes criados num determinado período, utilize a chamada `POST /api/documents/date`:
```json
POST /api/documents/date
{
"startDateUtc": "2023-01-01",
"endDateUtc": "2023-01-31"
}
```
* startDateUtc - `string`: data e hora (opcional) do início do período a ser considerado.
* endDateUtc - `string`: data e hora (opcional) do fim do período a ser considerado.
Como resposta são enviados os totais de documentos e envelopes criados:
```json
{
"totalDocuments": 0,
"totalEnvelopes": 0
}
```
# [Guia de Integração](https://docs.lacunasoftware.com/articles/signer/integration-guide)
O Signer é um portal de documentos que permite a realização de assinaturas digitais e eletrônicas, bem como a criação de fluxos complexos. Este guia apresenta as opções de integração para que sua aplicação aproveite ao máximo o potencial do Signer.
> **Dica: Novo por aqui?**
>
> Se é a sua primeira integração, comece pelos [Primeiros passos](https://docs.lacunasoftware.com/articles/signer/get-started), que levam você do zero ao primeiro documento assinado em cerca de 15 minutos. Este guia serve como referência dos casos de uso.
## Autenticação
Todas as chamadas de API exigem uma chave de acesso de API (*API Key*). Essa chave deve ser colocada no cabeçalho de todas as requisições:
```javascript
X-Api-Key: sua-aplicacao|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
Toda chave de API está associada a uma Organização previamente cadastrada no Signer. Sendo assim, o escopo dessa chave fica **restrito àquela organização**, isto é, aquela chave só permitirá acessar/criar documentos daquela organização.
> **Observação**
>
> Para obter uma chave de API: se você tem permissão para criar aplicações na organização, crie uma aplicação em **Aplicações** e gere a chave com o botão **Chaves** dela, como mostram os [Primeiros passos](https://docs.lacunasoftware.com/articles/signer/get-started#passo-1-crie-a-aplicação-e-a-chave-de-api). Caso contrário, peça uma chave ao administrador da sua instância.
## Bibliotecas cliente
A Lacuna publica uma biblioteca cliente da API para .NET, Java, Node.js e PHP. Você cria o cliente com a URL da sua instância do Signer e a chave de API, e chama as APIs pelos métodos dele. Também é possível chamar a API REST diretamente de qualquer linguagem, como fazem os [Primeiros passos](https://docs.lacunasoftware.com/articles/signer/get-started) com curl.
| Linguagem | Pacote | Versão | Instalação |
| --- | --- | --- | --- |
| .NET (.NET Framework 4.5 ou posterior, .NET Standard 2.0) | [`Lacuna.Signer.Client`](https://www.nuget.org/packages/Lacuna.Signer.Client) | 1.24.0 | `dotnet add package Lacuna.Signer.Client` |
| Java | [`com.lacunasoftware.signer:signer-client`](https://central.sonatype.com/artifact/com.lacunasoftware.signer/signer-client) | 2.14.0 | Gradle: `implementation 'com.lacunasoftware.signer:signer-client:2.14.0'` |
| Node.js | [`signer-node-client`](https://www.npmjs.com/package/signer-node-client) | 1.1.0 | `npm install signer-node-client` |
| PHP | [`lacuna/signer-client`](https://packagist.org/packages/lacuna/signer-client) | 1.4.10 | `composer require lacuna/signer-client` |
Os [exemplos no GitHub](https://github.com/LacunaSoftware/SignerSamples) usam essas bibliotecas, com um cenário por caso de uso deste guia. Para embutir a tela de assinatura na sua página web, use a biblioteca JavaScript `lacuna-signer-widget`: veja [Assinatura embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature).
## Como o fluxo funciona
Na prática, uma integração com o Signer segue quatro etapas. Cada etapa tem uma página dedicada neste guia:
1. **[Criar documentos](https://docs.lacunasoftware.com/articles/signer/integration/documents)**: envie o arquivo, monte o fluxo de participantes e (opcionalmente) assine dentro da sua própria aplicação.
2. **[Acompanhar documentos](https://docs.lacunasoftware.com/articles/signer/integration/tracking)**: verifique o status, envie lembretes e receba notificações por webhook.
3. **[Baixar e listar](https://docs.lacunasoftware.com/articles/signer/integration/download-and-list)**: obtenha as versões do documento e liste documentos por diferentes critérios.
4. **[Validar assinaturas](https://docs.lacunasoftware.com/articles/signer/integration/validation)**: valide as assinaturas por chave de validação ou pelo arquivo assinado.
E, ao longo da vida do documento, você pode precisar de operações de **[ciclo de vida](https://docs.lacunasoftware.com/articles/signer/integration/lifecycle)**: cancelar, recusar, enviar nova versão ou deletar.
## Casos de uso
| Caso de uso | O que você faz |
| --- | --- |
| [Criar documentos](https://docs.lacunasoftware.com/articles/signer/integration/documents) | Upload, criação do fluxo, assinatura embutida, pré-posicionamento de assinaturas e envelopes (múltiplos arquivos) |
| [Acompanhar documentos](https://docs.lacunasoftware.com/articles/signer/integration/tracking) | Verificar status do documento e dos participantes, enviar lembretes e configurar webhooks |
| [Baixar e listar](https://docs.lacunasoftware.com/articles/signer/integration/download-and-list) | Baixar as diferentes versões do documento e listar documentos |
| [Validar assinaturas](https://docs.lacunasoftware.com/articles/signer/integration/validation) | Validar assinaturas por chave ou por arquivo assinado |
| [Ciclo de vida do documento](https://docs.lacunasoftware.com/articles/signer/integration/lifecycle) | Cancelar, recusar, enviar nova versão e deletar |
## Respostas de erro
As APIs respondem com estes códigos de status HTTP:
| Código | Significado |
| --- | --- |
| `200` (OK) | Requisição processada. O corpo da resposta depende da API: veja a entrada dela na [referência da API](https://www.dropsigner.com/swagger). |
| `400` (Bad Request) | Erro de sintaxe, por exemplo um campo obrigatório que não foi enviado. |
| `401` (Unauthorized) | A chave de API não foi enviada ou é inválida. |
| `403` (Forbidden) | A chave de API é válida, mas a aplicação dela não tem permissão para a operação pedida. |
| `422` (Unprocessable Entity) | Erro da API, por exemplo um ID de entidade que não foi encontrado. O corpo é um `ErrorModel`. |
Um `ErrorModel` tem três campos:
| Campo | Tipo | Conteúdo |
| --- | --- | --- |
| `code` | string | O código do erro. Teste este campo no seu código, e não a mensagem. |
| `message` | string | Uma descrição do erro. |
| `details` | objeto, com valores string | Informações adicionais sobre o erro. Pode ser `null`. |
```json
{
"code": "DocumentNotFound",
"message": "...",
"details": null
}
```
A referência da API lista os principais códigos de uma resposta `422`: `CertificateNotFound`, `DocumentNotFound`, `FolderNotFound`, `CpfMismatch`, `CpfNotExpected`, `InvalidFlowAction` e `DocumentInvalidKey`. A lista não é completa, e novos códigos podem ser incluídos a qualquer momento sem aviso, então trate também os códigos que você não conhece. Outras páginas descrevem os códigos das suas operações, como `DocumentHasNoSignatures` ao [baixar uma versão](https://docs.lacunasoftware.com/articles/signer/integration/download-and-list#quando-uma-versão-não-pode-ser-baixada) e `MaxWebhooksLimitReached` ao [cadastrar um webhook](https://docs.lacunasoftware.com/articles/signer/webhooks#pela-api).
## Links Úteis
* [Referência da API (Swagger)](https://www.dropsigner.com/swagger)
* [Exemplos no GitHub](https://github.com/LacunaSoftware/SignerSamples)
* [Assinatura embutida (Widget)](https://docs.lacunasoftware.com/articles/signer/embedded-signature)
* [Webhooks](https://docs.lacunasoftware.com/articles/signer/webhooks)
**Demonstrações e exemplos**
- Generate a document from a PDF form template and CSV data: [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/GenerateDocumentWithTwoOrMoreSignersScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/GenerateDocumentWithTwoOrMoreSignersScenario.php)
- List saved document flows: [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/listDocumentScenario.ts)
# [Criar documentos](https://docs.lacunasoftware.com/articles/signer/integration/documents)
Criar um documento no Signer envolve dois passos: **enviar o arquivo** (upload) e **criar o documento** definindo o fluxo de participantes. A partir daí, o Signer notifica automaticamente os participantes na ordem definida.
## Passo a passo
### 1. Upload do arquivo
Faça o upload do arquivo a ser assinado. Há duas APIs de upload:
* A [API simplificada de Upload (POST /api/uploads/bytes)](https://www.dropsigner.com/swagger/index.html#operations-Upload-post_api_uploads_bytes) recebe uma requisição JSON com os bytes do arquivo em Base64, no campo `bytes`. É a opção mais simples, a que os [Primeiros passos](https://docs.lacunasoftware.com/articles/signer/get-started) usam.
* A [API de Upload (POST /api/uploads)](https://www.dropsigner.com/swagger/index.html#operations-Upload-post_api_uploads) recebe o arquivo em uma requisição [multipart/form-data](https://ec.haxx.se/http/http-multipart). Prefira-a para arquivos grandes, porque evita o custo de transportar o arquivo em Base64.
As duas retornam um ID de upload que identifica aquele arquivo:
```javascript
{
...
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```
### 2. Criação do documento
Crie um documento a partir do upload usando a [API de Criação de Documentos](https://www.dropsigner.com/swagger/index.html#operations-Documents-post_api_documents). Nessa chamada você monta o **fluxo do documento**, isto é, define os participantes e em qual ordem devem tomar suas ações:
```javascript
POST /api/documents
{
"files": [
{
"displayName": "Contrato Integração", //Nome que será dado ao documento criado
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", //ID do upload
"name": "Contrato.pdf", //Nome do arquivo original
"contentType": "application/pdf" //mime type do arquivo original
}
],
"flowActions": [
{
"type": "Signer", //Tipo do participante, nesse caso, assinante
"step": 1, //Ordem de assinatura
"user": {
"name": "John Wick", //Nome do participante
"identifier": "81976153069", //CPF do participante
"email": "john.wick@mailinator.com", //Email do participante
},
"allowElectronicSignature": true //Permite assinatura eletrônica
}
]
}
```
A resposta retorna o ID do documento criado associado ao ID do upload, assim você sabe exatamente qual documento corresponde a qual upload:
```javascript
[
{
"uploadId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"documentId": "b12cb1b2-5d6e-40b2-a050-097d068c4c11"
}
]
```
> **Dica**
>
> Você pode criar mais de um documento na mesma chamada, adicionando quantos arquivos forem necessários. Nesse caso, todos os documentos terão o mesmo fluxo.
Ao criar o documento, o Signer notifica automaticamente os participantes seguindo a ordem especificada. Cada participante receberá um e-mail com um link que permite assinar ou aprovar o documento sem precisar de autenticação.
## O fluxo (`flowActions`)
Cada item de `flowActions` representa uma ação de um participante. Os principais campos são:
| Campo | Descrição |
| --- | --- |
| `type` | Tipo da ação (veja a tabela abaixo) |
| `step` | Ordem da ação no fluxo. Ações com o mesmo `step` acontecem em paralelo; `step`s diferentes são executados em sequência |
| `user` | Dados do participante (`name`, `identifier`, `email`) |
| `allowElectronicSignature` | Permite que o participante assine eletronicamente (sem certificado digital) |
Os tipos de ação (`type`) disponíveis são:
| `type` | Descrição |
| --- | --- |
| `Signer` | Participante que **assina** o documento |
| `Approver` | Participante que **aprova** o documento (sem assinar) |
| `SignRule` | Regra de assinatura: um grupo em que qualquer membro pode assinar |
| `ApproverRule` | Regra de aprovação: um grupo em que qualquer membro pode aprovar |
**Exemplos no GitHub**, por cenário:
| Cenário | Exemplos |
| --- | --- |
| Um assinante | [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithOneSignerScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithOneSignerScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithOneSignerScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithOneSignerScenario.ts) |
| Dois ou mais assinantes, com ordenação | [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithTwoOrMoreSignersWithOrderScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithTwoOrMoreSignersWithOrderScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithTwoOrMoreSignersWithOrderScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithTwoOrMoreSignersWithOrderScenario.ts) |
| Dois ou mais assinantes, sem ordenação | [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithTwoOrMoreSignersWithoutOrderScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithTwoOrMoreSignersWithoutOrderScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithTwoOrMoreSignersWithoutOrderScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithTwoOrMoreSignersWithoutOrderScenario.ts) |
| Com aprovação | [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithApproversScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithApproversScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithApproversScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithApproversScenario.ts) |
| Com anexo | [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithAttachmentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithAttachmentScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithAttachmentScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithAttachmentScenario.ts) |
| Com descrição | [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithDescriptionScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithDescriptionScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithDescriptionScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithDescriptionScenario.ts) |
## Assinatura na sua própria aplicação
Caso você queira realizar a assinatura do documento dentro da sua própria aplicação, use a opção de **Assinatura Embutida**.
Siga os mesmos passos da seção anterior, mas, ao enviar o documento, recomenda-se adicionar o parâmetro `disablePendingActionNotifications` com valor `true`. Dessa forma, não serão enviadas notificações para os participantes.
Após a criação do documento, utilize o ID do documento para obter a URL de assinatura usando a [API de URL de Ação](https://www.dropsigner.com/swagger/index.html#operations-Documents-post_api_documents__id__action_url):
```javascript
POST /api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11/action-url
{
//devem ser enviadas informações que identifiquem o participante desejado
"identifier": "81976153069",
"emailAddress": "john.wick@mailinator.com"
}
```
Caso o signatário tenha permitido assinatura eletrônica (parâmetro `allowElectronicSignature = true`), é possível exigir a confirmação do e-mail dele por meio de um código de acesso no momento da assinatura. Para isso, defina `requireEmailAuthentication` como `true`:
```javascript
POST /api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11/action-url
{
//devem ser enviadas informações que identifiquem o participante desejado
"identifier": "81976153069",
"emailAddress": "john.wick@mailinator.com",
//se a assinatura for eletrônica e esse parâmetro for true, requer confirmação de e-mail com código para completar a assinatura
"requireEmailAuthentication": true
}
```
A resposta apresenta duas URLs:
```javascript
{
//URL para redirecionar o usuário para a página de assinatura no Signer
"url": "https://...",
//URL para usar com o Widget de assinatura
"embedUrl": "https://..."
}
```
Use a `embedUrl` com o **Widget de assinatura** para exibir a página de assinatura do Signer dentro da sua aplicação. A página [Assinatura embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature) descreve como utilizar o *Widget*.
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/EmbeddedSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/EmbeddedSignatureScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/EmbeddedSignatureScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/embeddedSignatureScenario.ts)
## Pré-posicionar uma assinatura no documento
Durante a criação de um documento, é possível posicionar a assinatura de cada participante em um local específico do documento. Todas as definições são feitas durante a criação do `flowAction`: há opções para definir a página e a localização da assinatura dentro dela.
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithPositionedSignaturesScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithPositionedSignaturesScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithPositionedSignaturesScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithPositionedSignaturesScenario.ts)
## Mesclar múltiplos arquivos em um único documento (envelope)
É possível fazer o upload de múltiplos arquivos e mesclar todos em um só documento (envelope). Para isso, adicione o parâmetro `isEnvelope=true` e um nome de envelope em `EnvelopeName`.
> **Cuidado**
>
> A mesclagem só funciona se **todos os arquivos enviados forem PDFs**.
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithEnvelopeScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithEnvelopeScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithEnvelopeScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithEnvelopeScenario.ts)
## Próximos passos
Com o documento criado, veja como [acompanhar o status e enviar lembretes](https://docs.lacunasoftware.com/articles/signer/integration/tracking).
**Demonstrações e exemplos**
- Create a document with an attachment: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithAttachmentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithAttachmentScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithAttachmentScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithAttachmentScenario.php)
- Create a document with a description: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithDescriptionScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithDescriptionScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithDescriptionScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithDescriptionScenario.php)
- Merge several PDFs into one document (envelope): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithEnvelopeScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithEnvelopeScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithEnvelopeScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithEnvelopeScenario.php)
- Upload a file as Base64: [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateBase64DocumentScenario.php)
- Upload a file as multipart/form-data: [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/uploadUsingMultipartFormDataScenario.ts)
- Create a document with two or more signers, in any order: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithTwoOrMoreSignersWithoutOrderScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithTwoOrMoreSignersWithoutOrderScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithTwoOrMoreSignersWithoutOrderScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithTwoOrMoreSignersWithoutOrderScenario.php)
- Create a document with signers in a fixed order: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithTwoOrMoreSignersWithOrderScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithTwoOrMoreSignersWithOrderScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithTwoOrMoreSignersWithOrderScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithTwoOrMoreSignersWithOrderScenario.php)
- Create a document with an approver: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithApproversScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithApproversScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithApproversScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithApproversScenario.php)
- Create a document with a signing rule (any N of a group): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithSigningRuleScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithSigningRuleScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithSigningRuleScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithSigningRuleScenario.php)
- Pre-position a signature on the document: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithPositionedSignaturesScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithPositionedSignaturesScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithPositionedSignaturesScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithPositionedSignaturesScenario.php)
- Add a signer to an existing document: [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/AddSignerToExistingDocumentScenario.php)
- Change a participant in an ongoing flow: [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/DocumentFlowEditRequestScenario.php)
- Require a CAdES signature on a PDF: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreatePDFDocumentWithCadesSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreatePDFDocumentWithCadesSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createPDFDocumentWithCadesSignatureScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreatePDFDocumentWithCadesSignatureScenario.php)
- Sign a whole XML file (XAdES): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateXMLWithFullDocumentSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateXmlWithFullDocumentSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createXmlWithFullDocumentSignatureScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateXmlWithFullDocumentSignatureScenario.php)
- Sign one element of an XML file (XAdES): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateXMLWithElementSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateXMLWithElementSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createXMLWithElementSignatureScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateXMLWithElementSignatureScenario.php)
- Sign a Brazilian digital diploma (XAdES, Portaria MEC 554/2019): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDigitalDegreeScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDigitalDegreeScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDigitalDegreeScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDigitalDegreeScenario.php)
- Sign with a digital certificate through the API (public signature): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/PublicSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/PublicSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/publicSignatureScenario.ts)
- Sign electronically with an uploaded signature image: [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/ElectronicSignatureWithSignatureImageScenario.java)
- Create a document in a new folder: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentInNewFolderScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentInNewFolderScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentInNewFolderScenario.php)
- Create a document in an existing folder: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentInExistingFolderScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentInExistingFolder.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentInExistingFolderScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentInExistingFolder.php)
# [Acompanhar documentos](https://docs.lacunasoftware.com/articles/signer/integration/tracking)
Depois de criar um documento, você acompanha o andamento dele de duas formas: **consultando o status** ativamente (polling) ou **recebendo notificações** por webhook quando algo acontece. Também é possível enviar lembretes aos participantes que ainda não agiram.
## Verificar o status de um documento
Para verificar o status de um documento é preciso conhecer o seu ID. Em seguida, faça uma chamada à [API de Detalhes do Documento](https://www.dropsigner.com/swagger/index.html#operations-Documents-get_api_documents__id_):
```javascript
GET /api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11
{
"id": "b12cb1b2-5d6e-40b2-a050-097d068c4c11",
"name": "Contrato Integração",
"status": "Concluded",
"creationDate": "2019-08-18T16:26:03.372Z",
"updateDate": "2019-08-18T16:26:03.372Z",
...
"flowActions": [
{
"id": "4bf61c68-eaf1-455f-b4a1-6141554f1dae",
"type": "Signer",
"status": "Completed",
"step": 1,
...
"user":
{
"id": "4d961566-9b03-450c-b144-930e0294bac2",
"name": "John Wick",
"identifier": "81976153069",
"email": "john.wick@mailinator.com"
},
"title": "Parte",
"allowElectronicSignature": true
}
]
}
```
* Para saber se o documento está concluído, verifique se a propriedade `status` corresponde a `Concluded`.
* Para saber se um participante assinou/aprovou, verifique se a propriedade `status` corresponde a `Completed` no elemento correspondente da lista `flowActions`.
### Status do documento
A propriedade `status` do documento pode assumir os seguintes valores:
| Status | Significado |
| --- | --- |
| `Pending` | Aguardando ações dos participantes |
| `FlowConcluded` | Todas as ações do fluxo foram concluídas (processamento final em andamento) |
| `Concluded` | Documento concluído |
| `Refused` | Documento recusado por um participante |
| `Canceled` | Documento cancelado |
| `Expired` | Documento expirado |
### Status da ação do participante
Cada item de `flowActions` tem seu próprio `status`:
| Status | Significado |
| --- | --- |
| `Created` | Ação criada, ainda não liberada para o participante (aguardando etapa anterior) |
| `Pending` | Aguardando a ação do participante |
| `Completed` | Participante concluiu a ação (assinou/aprovou) |
| `Refused` | Participante recusou |
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CheckDocumentStatusScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CheckDocumentStatusScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CheckDocumentStatusScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/checkDocumentStatusScenario.ts)
## Enviar lembretes de assinatura
Uma vez verificado o status de um participante, você pode enviar lembretes periódicos aos participantes que ainda não completaram uma ação, usando a [API de Envio de Lembretes](https://www.dropsigner.com/swagger/index.html#operations-Notifications-post_api_notifications_flow_action_reminder). Informe o ID do documento e o ID da ação do participante (`flowActionId`):
```javascript
POST /api/notifications/flow-action-reminder
{
"documentId": "b12cb1b2-5d6e-40b2-a050-097d068c4c11",
"flowActionId": "4bf61c68-eaf1-455f-b4a1-6141554f1dae"
}
```
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/NotifyFlowParticipantsScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/NotifyFlowParticipantsScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/NotifyFlowParticipantsScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/notifyFlowParticipantsScenario.ts)
## Webhooks
Em vez de consultar o status repetidamente, você pode configurar um **Webhook** que será disparado toda vez que um documento da sua organização for concluído, aprovado, recusado ou assinado. Para isso, acesse a tela da Organização e registre a URL desejada na opção **Integração**:

Veja [Webhooks](https://docs.lacunasoftware.com/articles/signer/webhooks) para a lista completa de eventos, o formato do payload de cada um e o comportamento de entrega (autenticação, retentativas e ordem).
## Próximos passos
Quando o documento estiver concluído, veja como [baixar e listar documentos](https://docs.lacunasoftware.com/articles/signer/integration/download-and-list).
**Demonstrações e exemplos**
- Check a document's status and its participants' actions: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CheckDocumentStatusScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CheckDocumentStatusScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CheckDocumentStatusScenario.php)
- Send reminders to participants with pending actions: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/NotifyFlowParticipantsScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/NotifyFlowParticipantsScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/notifyFlowParticipantsScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/NotifyFlowParticipantsScenario.php)
# [Baixar e listar documentos](https://docs.lacunasoftware.com/articles/signer/integration/download-and-list)
## Download das versões de um documento
A partir do momento em que um documento é criado, é possível baixar diferentes versões dele, de acordo com o estágio em que ele se encontra. Utilize a [API de Download de Documentos](https://www.dropsigner.com/swagger/index.html#operations-Documents-get_api_documents__id__content), informando o tipo desejado no parâmetro `type`:
```javascript
GET /api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11/content?type=Original
```
### Tipos de versão (`type`)
| `type` | Descrição |
| --- | --- |
| `Original` | Arquivo original enviado |
| `Signatures` | Arquivo assinado |
| `PrinterFriendlyVersion` | Versão para impressão |
| `SigningTags` | Etiquetas de assinatura |
| `SignatureMarks` | Marcas de assinatura |
| `OriginalWithMarks` | Arquivo original com as marcas de assinatura |
| `SignaturesManifest` | Manifesto de assinaturas |
> **Observação: Formato de retorno**
>
> Existem dois métodos com tipos de retorno diferentes: um retorna uma `Stream` de dados referente ao documento e o outro retorna o documento em um vetor de `bytes[]`.
### Quando uma versão não pode ser baixada
* `Signatures` e `PrinterFriendlyVersion` só existem depois que pelo menos um participante assinou. Para um documento que ninguém assinou ainda, a API responde `422` com o código de erro `DocumentHasNoSignatures`. Enquanto o documento não está `Concluded`, `Signatures` traz as assinaturas coletadas até o momento.
* Documentos cancelados, expirados ou recusados só podem ser baixados como `Original` e `PrinterFriendlyVersion`.
Antes de baixar, consulte o documento com a [API de Detalhes do Documento](https://www.dropsigner.com/swagger/index.html#operations-Documents-get_api_documents__id_) e verifique os campos `hasSignature` e `status`, como fazem os exemplos. O formato do erro está em [Respostas de erro](https://docs.lacunasoftware.com/articles/signer/integration-guide#respostas-de-erro).
> **Aviso: Cliente PHP**
>
> No cliente PHP, `getDocumentContent` não lança exceção em erros HTTP: ele devolve o corpo da resposta de erro, o JSON do `ErrorModel`, como se fosse o arquivo. Salvo sem verificação, esse conteúdo vira um "arquivo" que contém o erro. Consulte o documento antes, como descrito acima.
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/DownloadDocumentVersionScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/DownloadDocumentVersionScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/DownloadDocumentVersionScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/downloadDocumentVersionScenario.ts)
## Listar documentos
É possível listar os documentos de acordo com diferentes padrões e necessidades. Os parâmetros disponíveis estão descritos na [API de Listagem de Documentos](https://www.dropsigner.com/swagger/index.html#operations-Documents-get_api_documents). Entre os tipos de listagem possíveis:
* Documentos pendentes para um determinado participante
* Documentos concluídos
* Listar por pastas
* Listar por organização
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/ListDocumentsScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/ListDocumentsScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/ListDocumentsScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/listDocumentScenario.ts)
**Demonstrações e exemplos**
- List documents: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/ListDocumentsScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/ListDocumentsScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/listDocumentsScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/ListDocumentsScenario.php)
- Download a document's versions: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/DownloadDocumentVersionScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/DownloadDocumentVersionScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/downloadDocumentVersionScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/DownloadDocumentVersionScenario.php)
# [Validar assinaturas de um documento](https://docs.lacunasoftware.com/articles/signer/integration/validation)
Você pode validar as assinaturas de um documento de duas formas:
* com a **chave de validação do documento**, caso ele tenha sido assinado nesta instância do Signer;
* com o **arquivo assinado**, útil para validar documentos assinados em qualquer lugar.
## Validação com chave de validação
Para validar um documento com chave de validação, use a [API de Validação de Chave](https://www.dropsigner.com/swagger/index.html#operations-Documents-get_api_documents_keys__key__signatures) informando a chave correspondente.
Serão retornados os dados básicos do documento e as informações de cada uma das assinaturas encontradas:
```javascript
GET /api/documents/keys/AX4F8FV8NNAX25TENE2S/signatures
{
"id": "b12cb1b2-5d6e-40b2-a050-097d068c4c11",
"name": "Contrato Integração",
"filename": "Contrato.pdf",
"mimeType": "application/pdf",
"isConcluded": true,
...
"creationDate": "2020-08-18T16:38:40.538Z",
"updateDate": "2020-08-18T16:38:40.538Z",
"signers": [
{
"subjectName": "John Wick",
"emailAddress": "john.wick@mailinator.com",
"issuerName": "Lacuna CA",
"identifier": "81976153069",
"companyName": null,
"companyIdentifier": null,
"isElectronic": false,
"signingTime": "2020-08-18T16:38:40.538Z",
"certificateThumbprint": "a0sRR9cWOc0PORMhTBg49ub/5BO3W5vWQ1w7+YquK5g=",
...
"validationResults":
{
...
"isValid": true
}
}
]
}
```
## Validação com arquivo assinado
Para validar um documento a partir do arquivo assinado, primeiro faça o upload do arquivo usando a [API de Upload](https://www.dropsigner.com/swagger/index.html#operations-Upload-post_api_uploads) ou a [API simplificada de Upload (POST /api/uploads/bytes)](https://www.dropsigner.com/swagger/index.html#operations-Upload-post_api_uploads_bytes), assim como descrito em [Criar documentos](https://docs.lacunasoftware.com/articles/signer/integration/documents).
Em seguida, utilize a [API de validação de arquivo assinado](https://www.dropsigner.com/swagger/index.html#operations-Documents-post_api_documents_validate_signatures):
```javascript
POST /api/documents/validate-signatures
{
"fileId": "f5ea05d7-0a5f-4933-a6d6-9a8aa3955b14",
"mimeType": "application/pdf"
}
```
Serão retornados os dados de cada uma das assinaturas encontradas no documento:
```javascript
[
{
"subjectName": "John Wick",
"emailAddress": "john.wick@mailinator.com",
"issuerName": "Lacuna CA",
"identifier": "81976153069",
"companyName": null,
"companyIdentifier": null,
"isElectronic": false,
"signingTime": "2020-08-18T16:38:40.538Z",
"certificateThumbprint": "a0sRR9cWOc0PORMhTBg49ub/5BO3W5vWQ1w7+YquK5g=",
...
"validationResults":
{
...
"isValid": true
}
}
]
```
> **Dica**
>
> Em ambas as formas, verifique `validationResults.isValid` de cada assinatura para saber se ela é válida.
# [Ciclo de vida do documento](https://docs.lacunasoftware.com/articles/signer/integration/lifecycle)
Além de criar e acompanhar, você pode realizar operações ao longo da vida do documento: cancelar, recusar, enviar uma nova versão ou deletar.
## Cancelar documento
Para cancelar um documento, envie o ID do documento em uma requisição `POST` junto ao campo `reason`:
```javascript
POST /api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11/cancellation
{
"reason": "This is a document cancellation"
}
```
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CancelDocumentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CancelDocumentScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CancelDocumentScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/cancelDocumentScenario.ts)
## Recusar documento
Para recusar um documento, envie o ID do documento em uma requisição `POST` junto ao campo `reason`:
```javascript
POST /api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11/refusal
{
"reason": "This is a document refusal"
}
```
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/RefuseDocumentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/RefuseDocumentScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/RefuseDocumentScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/refuseDocumentScenario.ts)
## Enviar nova versão do documento
É possível enviar uma nova versão de um documento já criado. Para isso, envie o ID do documento em uma requisição `POST` junto ao arquivo da nova versão.
> **Cuidado**
>
> Ao enviar uma nova versão, o **fluxo de assinaturas é reiniciado**.
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/AddNewDocumentVersionScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/AddNewDocumentVersionScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/AddNewDocumentVersionScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/addNewDocumentVersionScenario.ts)
## Deletar documento
Para deletar um documento, você precisa do ID dele e faz uma chamada do tipo `DELETE`:
```javascript
DELETE /api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11
```
**Exemplos no GitHub:** [C#](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/DeleteDocumentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/DeleteDocumentScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/DeleteDocumentScenario.php) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/deleteDocumentScenario.ts)
**Demonstrações e exemplos**
- Cancel a document: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CancelDocumentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CancelDocumentScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/cancelDocumentScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CancelDocumentScenario.php)
- Refuse a document as the organization: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/RefuseDocumentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/RefuseDocumentScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/refuseDocumentScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/RefuseDocumentScenario.php)
- Delete a document: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/DeleteDocumentScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/DeleteDocumentScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/deleteDocumentScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/DeleteDocumentScenario.php)
- Upload a new version of a document: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/AddNewDocumentVersionScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/AddNewDocumentVersionScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/addNewDocumentVersionScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/AddNewDocumentVersionScenario.php)
# [Assinar documentos em lote](https://docs.lacunasoftware.com/articles/signer/integration/batch-signing)
A sua aplicação pode fazer o usuário assinar vários documentos do Signer com o certificado dele, confirmando uma única vez. O Signer não tem
um endpoint de assinatura em lote: a sua aplicação faz as chamadas de *assinatura pública* do Signer, início e conclusão, uma vez por
documento, enquanto o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) assina cada hash no navegador depois de uma única confirmação.
> **Aviso: Fora da referência da API**
>
> Os endpoints de assinatura pública desta página não estão na referência da API do Signer (Swagger). Os samples da Lacuna os descrevem como
> suportados para integrações de clientes; a única fonte sobre assinatura em lote é o sample em Java
> [`batch-pades-signature-signer`](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchSignerSignatureController.java).
## Antes de começar
* O seu servidor chama o Signer com uma chave de API, como no restante do [Guia de Integração](https://docs.lacunasoftware.com/articles/signer/integration-guide).
* A sua página carrega o Web PKI para listar os certificados do usuário e assinar os hashes.
* O certificado precisa pertencer ao participante: o Signer verifica se ele tem o CPF do participante e, se não tiver, falha com
`CpfMismatch`. O sample cria cada documento com o CPF lido do certificado que o usuário escolheu.
## Fluxo
1. **Servidor**: para cada documento, obtenha a URL de ação do participante e extraia dela a **chave** (key) e o **ticket** do documento.
1. **Navegador**: o Web PKI lê o certificado do usuário e pede que ele autorize todas as assinaturas de uma vez.
1. **Servidor**: para cada documento, inicie a assinatura com a chave, o ticket e o certificado; o Signer devolve o hash a assinar e um
token.
1. **Navegador**: o Web PKI assina o hash.
1. **Servidor**: conclua a assinatura com a chave, a assinatura e o token.
## 1. Obter a chave e o ticket de cada documento
Os documentos precisam já existir no Signer, com o usuário como signatário no fluxo deles (veja [Criar documentos](https://docs.lacunasoftware.com/articles/signer/integration/documents)); o
sample cria dez, só para a demonstração.
Para cada documento, o sample chama `POST /api/documents/{id}/action-url` com o `emailAddress` e o `identifier` do participante e extrai a
chave e o ticket do `embedUrl` devolvido, cujo formato é `…/document/key/{key}/sign-embedded?ticket={ticket}`:
```java
private static final Pattern KEY_PATTERN = Pattern.compile("/key/([^/?]+)");
private static final Pattern TICKET_PATTERN = Pattern.compile("[?&]ticket=([^&]+)");
```
```java
List results = signerClient.createDocument(documentRequest);
UUID documentId = results.get(0).getDocumentId();
ActionUrlRequest actionUrlRequest = new ActionUrlRequest();
actionUrlRequest.setEmailAddress(SIGNER_EMAIL);
actionUrlRequest.setIdentifier(cpf);
ActionUrlResponse actionUrlResponse = signerClient.getActionUrl(documentId, actionUrlRequest);
String embedUrl = actionUrlResponse.getEmbedUrl();
String key = extractGroup(KEY_PATTERN, embedUrl, "key");
String ticket = extractGroup(TICKET_PATTERN, embedUrl, "ticket");
```
A chave vai na URL das chamadas de início e de conclusão; o ticket autoriza a assinatura e vai na requisição de início. Guarde o ticket no
servidor, associado à chave: o sample o guarda na sessão HTTP.
* O formato da chave e do ticket só aparece nos comentários dos samples, não na referência da API.
* Para criar muitos documentos, a referência da API recomenda várias requisições (para 100 documentos, 10 requisições de 10), com
`disablePendingActionNotifications` definido, e depois `POST /api/users/notify-pending` quando todos estiverem criados.
## 2. Autorizar todas as assinaturas de uma vez
No navegador, o sample lê o certificado escolhido e chama o `preauthorizeSignatures` do Web PKI com o número de documentos, para que o
usuário veja um único diálogo de confirmação:
```js
pki.readCertificate(selectedCertThumbprint).success(function (certEncoded) {
selectedCertContent = certEncoded;
pki.preauthorizeSignatures({
certificateThumbprint: selectedCertThumbprint,
signatureCount: formElements.documentsIds.length
}).success(startBatch); // Callback to be called if the user authorizes the signatures.
});
```
## 3. Iniciar, assinar e concluir cada assinatura
**Início.** O seu servidor envia o certificado e o ticket para `POST /api/documents/keys/{key}/signature/certificate`. A resposta traz
`toSignHash`, `digestAlgorithm` e `token`, além de `success` e `validationResults`:
```java
String ticket = StorageMock.getSignerTicket(session, request.getId());
PublicStartSignatureRequest startRequest = new PublicStartSignatureRequest();
startRequest.setCertificate(Base64.getDecoder().decode(request.getCertContent()));
startRequest.setTicket(ticket);
StartSignatureResponse result = signerClient.startPublicSignature(request.getId(), startRequest);
```
**Assinatura.** O navegador assina o hash com o `signHash` do Web PKI:
```js
pki.signHash({
thumbprint: formElements.certificateSelect.val(),
hash: step.toSignHash,
digestAlgorithm: step.digestAlgorithm
}).success(function (signature) {
step.signature = signature;
done(step);
}).error(function (error) {
renderFail(step, error);
done();
});
```
**Conclusão.** O seu servidor envia a assinatura e o token para `POST /api/documents/keys/{key}/signature`; a resposta traz `success` e
`validationResults`:
```java
CompleteSignatureRequest completeRequest = new CompleteSignatureRequest();
completeRequest.setSignature(Base64.getDecoder().decode(request.getSignature()));
completeRequest.setToken(request.getToken());
signerClient.completePublicSignature(request.getId(), completeRequest);
```
O certificado, o hash e a assinatura trafegam em Base64.
O sample executa as três etapas como um pipeline de filas, dois documentos por vez em cada etapa. Um documento que falha sai do pipeline e
os demais seguem, sem nova tentativa:
```js
startQueue.process(startSignature, {threads: 2, output: performQueue});
performQueue.process(performSignature, {threads: 2, output: completeQueue});
completeQueue.process(completeSignature, {threads: 2, completed: onBatchCompleted});
```
Depois da conclusão, baixe cada documento assinado pelo seu servidor, por exemplo com `GET /api/documents/{id}/content?type=Signatures`,
para que a chave de API nunca chegue ao navegador. Veja [Baixar e listar documentos](https://docs.lacunasoftware.com/articles/signer/integration/download-and-list).
## O que as fontes ainda não dizem
* TODO(docs): o formato do algoritmo de hash: o sample em Java diz que o Signer devolve um OID (como `2.16.840.1.101.3.4.2.1`) e o converte
para o nome que o Web PKI espera (como `SHA-256`), enquanto o sample de assinatura pública em .NET espera um nome como `SHA256`.
* TODO(docs): limites de documentos por lote e de requisições (rate limiting) nas chamadas de assinatura; o sample só diz para ajustar a
concorrência ao seu plano do Signer.
* TODO(docs): por quanto tempo o ticket e o token valem, e se uma chamada de conclusão pode ser repetida.
* TODO(docs): os códigos de erro das chamadas de início e de conclusão, e o que significam `success` e `validationResults`.
* TODO(docs): se as chamadas `keys/{key}` exigem o header `X-Api-Key`; o cliente Java sempre o envia.
## Veja também
* [Criar documentos](https://docs.lacunasoftware.com/articles/signer/integration/documents)
* [Assinatura embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature), para assinar um documento por vez dentro da sua aplicação
* [Referência rápida do Signer](https://docs.lacunasoftware.com/articles/signer/quick-reference)
**Demonstrações e exemplos**
- Sign multiple Signer documents with Web PKI: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchSignerSignatureController.java)
# [Integração com Assinatura embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature)
## Introdução
A integração com Assinatura embutida permite assinar/aprovar documentos submetidos no Signer dentro de qualquer aplicação por meio do componente `LacunaSignerWidget`.
> **Observação**
>
> O Signer deve ser configurado com uma licença do Web PKI que tenha o domínio no qual o componente será carregado.
## Instalação
Primeiro, inclua na sua página o arquivo [lacuna-signer-widget.js](https://cdn.lacunasoftware.com/libs/signer/lacuna-signer-widget-0.8.0.min.js) mais recente:
```html
```
Em seguida, instancie a classe `LacunaSignerWidget`:
```javascript
var widget = new LacunaSignerWidget();
```
Chame então o método `on()` passando o tipo de evento que quer escutar e uma função de callback que será chamada quando o usuário assinar/aprovar o documento:
```javascript
//assinatura
widget.on(widget.events.documentSigned, function (e) {
// ...
//alert('Documento ' + e.id + ' assinado');
});
```
```javascript
//aprovação
widget.on(widget.events.documentApproved, function (e) {
// ...
//alert('Documento ' + e.id + ' aprovado');
});
```
Se preferir use o método `listen()` para escutar todos eventos com apenas um callback:
```javascript
widget.listen(function (e) {
if (e.type === widget.events.documentSigned) {
// ...
//alert('Documento ' + e.id + ' assinado');
} else if (e.type === widget.events.documentApproved) {
// ...
//alert('Documento ' + e.id + ' aprovado');
}
});
```
> **Observação**
>
> Sempre verifique no backend se o documento foi de fato concluído, dado que os callbacks podem ser acionados por outros arquivos javascript.
Por fim, carregue o iFrame utilizando o método `render()`, que recebe duas entradas obrigatórias:
* `embedUrl`: a URL para ação que deseja ser realizada. Corresponde ao parâmetro embedUrl retornado pela [API Action URL](https://www.dropsigner.com/swagger/index.html#operations-Documents-post_api_documents__id__action_url)
* `elementId`: id do elemento no qual será exibido o componente de assinatura.
E duas entradas opcionais:
* `page`: numero da página na qual o documento será aberto em sua inicialização (para abrir a última página de qualquer documento, não importando o tamanho dele, atribui-se o número `'-1'`).
* `scale`: a porcentagem que definirá em qual escala o documento será aberto. Pode ser definido através de um simples string `'100'` ou através do `widget.scaleOptions.S100`
```javascript
widget.render(embedUrl, 'embed-container', page, scale);
```
Caso prefira, pode ser fornecido o elemento DOM diretamente:
```javascript
widget.renderElement(embedUrl, document.getElementById('embed-container'), page, scale);
```
## Exemplo online
[Embedded content](https://jsfiddle.net/LacunaSoftware/hgutm4fL/embedded/)
Para executar o exemplo, clique na aba **Result**. Se você ainda não tem o Web PKI instalado, será preciso seguir as intruções para instalá-lo.
Ao usar este componente em sua própria aplicação, o usuário será redirecionado para a página de instalação e voltará à aplicação automaticamente ao final do processo.
## Exemplo online de assinatura em nuvem
Para este exemplo é utilizado o certificado de teste do BirdID (CPF 026.102.470-10, veja abaixo as instruções para pareamento).
[Embedded content](https://jsfiddle.net/LacunaSoftware/12zLfhgr/embedded/)
Para executar o exemplo, clique na aba **Result**. Para usar o usuário de teste do ambiente de homologação do BirdID use o QR code abaixo no aplicativo:

## Configurações adicionais
Na instanciação da classe `LacunaSignerWidget` você pode passar um objeto com uma ou mais das configurações listadas abaixo:
Atributo | Tipo | Descrição
----------------------- | ------------ | ----------
`enableNotifications` | boolean | Notificações não são enviadas após a conclusão da assinatura/aprovação embutida. Defina como `true` para enviá-las. Pode ser definido após a instanciação com método `setEnableNotifications`.
`disableDocumentPreview` | boolean | Se `true`, esconde a pré-visualização do documento. Nesse caso, será exibido apenas o botão de assinatura e dados do assinante/aprovador. Pode ser definido após a instanciação com método `setDisableDocumentPreview`.
`enableRefusal` | boolean | Se `true`, exibirá a opção para recusar uma assinatura. Pode ser definido após a instanciação com método `setEnableRefusal`.
`culture` | string | Define o idioma utilizado, as opções disponíveis são `"pt"`(português), `"en"`(inglês) e `"es"`(espanhol)
`theme` | string | Define o tema do widget, use o código de 3 caracteres de cada tema (ex: amazon-cornell-red: `"acr"`), os temas disponíveis estão na [página de temas](https://docs.lacunasoftware.com/articles/signer/on-premises/customization/temas)
Exemplo:
```javascript
var widget = new LacunaSignerWidget({ disableDocumentPreview: true });
```
```javascript
var widget = new LacunaSignerWidget();
widget.setDisableDocumentPreview(true);
```
## Exemplo online de assinatura sem pré-visualização
[Embedded content](https://jsfiddle.net/LacunaSoftware/pfn3zct1/embedded/)
## Exemplo para dispositivos móveis
Também é possível utilizar o exemplo de assinaturas embutidas em dispositivos móveis, um _app_ de exemplo está disponível em nosso [repositório do GitHub](https://github.com/LacunaSoftware/signer-flutter). O exemplo contém uma página da web contendo o widget em JavaScript rodando em seu dispositivo. A aplicação foi desenvolvida com [Flutter](https://flutter.dev/).
A imagem abaixo mostra o processo de assinatura sem preview de documento dentro do aplicativo:

## Posicionamento de marcas de assinatura
À partir da versão 1.70.0 do Signer e 0.7.0 do Widget, é possível embutir a tela de posicionamento de marcas de assinatura em outras aplicações tal como já era
feito para a tela de assinatura. Uma vez posicionadas, é possível obter os dados de posicionamento correspondente tanto pela biblioteca quando pela API do Signer
para que sejam criados um ou mais documentos com as posições selecionadas.
O primeiro passo é criar uma sessão de posicionamento utilizando a API de criação de sessão. Existem duas variações dessa API:
* [API de criação de sessão básica](https://www.dropsigner.com/swagger/index.html#operations-MarksSessions-post_api_marks_sessions): permite criar a sessão de posicionamento
fornecendo apenas os dados necessários para criação da sessão de posicionamento. Dados pessoais de signatários como e-mail e CPF, por exemplo, são opcionais. Use esta API, caso, após obter o resultado da
sessão, você ainda pretenda atualizar os dados da requisição de criação de documentos que será enviada posteriormente ao Signer.
* [API de criação de sessão com requisição de criação de documento](https://www.dropsigner.com/swagger/index.html#operations-MarksSessions-post_api_marks_sessions_documents):
permite criar a sessão de posicionamento fornecendo o mesmo request que é utilizado para criar documentos. Use esta API, caso prefira encaminhar o resultado da sessão diretamente
para a API de criação de documentos sem fazer nenhuma alteração adicional.
O resultado de ambas APIs será o retorno de um ID de sessão e uma URL para embutir a tela de posicionamento:
```javascript
{
//ID da sessão
"id": "4cdcfa93-8f38-46f9-8634-246d5589fe5f",
//URL para usar com o Widget
"embedUrl": "https://www.dropsigner.com/position-marks/4cdcfa93-8f38-46f9-8634-246d5589fe5f?v=1"
}
```
Uma vez obtida a URL de sessão, basta inicializar o Widget assim como é feito para a funcionalidade de assinatura:
```javascript
var widget = new LacunaSignerWidget();
```
Chame o método `on()` passando o evento de posicionamento de marcas e uma função de callback que será chamada quando o usuário concluir o posicionamento:
```javascript
//assinatura
widget.on(widget.events.marksPositioned, function (e) {
// O atributo data conterá a requisição preenchida com as posições das marcas
//console.log(e.data);
//alert('Marcas posicionadas');
});
```
Por fim, carregue o iFrame utilizando o método `render()`:
```javascript
widget.render(embedUrl, 'embed-container');
```
Uma vez concluída a sessão de posicionamento, cada entrada do atributo `flowActions` da requisição fornecida na criação da sessão será atualizada com as posições selecionadas na
tela de posicionamento (mais especificamente, será preenchido o atributo `prePositionedMarks`).
Essa requisição atualizada pode ser obtida pelo atributo `data` do evento de posicionamento ou pelo atributo de mesmo nome da
[API de obtenção de dados da sessão](https://www.dropsigner.com/swagger/index.html#operations-MarksSessions-get_api_marks_sessions__id_).
> **Observação**
>
> A sessão de posicionamento tem duração de 1 hora após sua criação. Após esse período, não será possível obter nem atualizar os dados da sessão.
**Demonstrações e exemplos**
- Demonstração: [Sign without leaving your page](https://try.lacunasoftware.com/pt/signer/embedded-signature)
- Sign without leaving your page (embedded signature): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/EmbeddedSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/EmbeddedSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/embeddedSignatureScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/EmbeddedSignatureScenario.php)
# [Webhooks](https://docs.lacunasoftware.com/articles/signer/webhooks)
## Introdução
Um **webhook** é uma URL da sua aplicação que o Signer chama, por `POST`, sempre que algo acontece com os
documentos da sua organização: um documento foi assinado, aprovado, recusado, concluído, cancelado,
expirou ou foi excluído.
É a forma recomendada de acompanhar documentos. Em vez de consultar `GET /api/documents/{id}`
repetidamente (*polling*), sua aplicação é avisada assim que o evento ocorre.
## Cadastrando um webhook
### Pela tela do Signer
Acesse a tela da **Organização**, seção **Integração**, e registre a URL desejada:

### Pela API
| Operação | Endpoint |
|---|---|
| Criar | `POST /api/organizations/{orgId}/webhooks` |
| Alterar | `PUT /api/organizations/{orgId}/webhooks/{webhookId}` |
| Remover | `DELETE /api/organizations/{orgId}/webhooks/{webhookId}` |
| Listar | Os webhooks cadastrados vêm no campo `webhooks` dos detalhes da organização (`GET /api/organizations/{orgId}`) |
Corpo da requisição de criação e alteração:
```json
{
"url": "https://suaaplicacao.com.br/integracoes/signer",
"authType": "Bearer",
"token": "um-segredo-longo-e-aleatorio"
}
```
A criação devolve o `id` do webhook, que é o `webhookId` usado para alterar ou remover.
> **Observação**
>
> * Uma organização pode ter vários webhooks, e todos recebem todos os eventos. O limite é definido pela
> configuração `MaxWebhooksPerOrganization` da instância (padrão: **5**); ao ultrapassá-lo a API responde
> com o erro `MaxWebhooksLimitReached`.
> * Webhooks não estão disponíveis em **organizações pessoais** (erro
> `OperationNotSupportedInPersonalOrganizations`).
## Autenticando a chamada
O Signer não assina o corpo da requisição. A autenticação é feita por um cabeçalho que você escolhe no
cadastro, com o valor informado em `token`:
| `authType` | Cabeçalho enviado |
|---|---|
| `NoAuth` | Nenhum |
| `Basic` | `Authorization: Basic ` |
| `Bearer` | `Authorization: Bearer ` |
| `ApiKey` | `X-Api-Key: ` |
> **Aviso**
>
> O `token` é enviado **exatamente como cadastrado**. No modo `Basic`, o Signer não codifica nada: você deve
> cadastrar o valor já em Base64 de `usuario:senha`.
>
> Use sempre HTTPS e um `authType` diferente de `NoAuth`, e valide o cabeçalho recebido antes de processar o
> evento. Sem isso, qualquer um que descubra a sua URL pode enviar eventos falsos.
## Formato da requisição
O Signer envia um `POST` com `Content-Type: application/json` (UTF-8). O corpo é sempre um envelope com o
tipo do evento e os dados específicos daquele tipo:
```json
{
"type": "DocumentConcluded",
"data": { }
}
```
Os nomes dos campos são em *camelCase* e os valores de enumerações são enviados como **texto**, por
exemplo `"DocumentConcluded"` e não `1`.
## Eventos
| `type` | Quando dispara |
|---|---|
| `DocumentsCreated` | Documentos foram criados (um evento pode trazer vários documentos) |
| `DocumentSigned` | Um participante assinou o documento |
| `DocumentApproved` | Um participante aprovou o documento |
| `DocumentRefused` | Um participante recusou o documento |
| `DocumentConcluded` | O fluxo terminou: todas as ações foram concluídas |
| `DocumentCanceled` | O documento foi cancelado |
| `DocumentExpired` | O documento atingiu a data de expiração sem ser concluído |
| `DocumentDeleted` | Documentos foram excluídos |
| `InvoiceClosed` | Uma fatura foi fechada (evento de faturamento, veja a observação abaixo) |
> **Observação**
>
> O tipo do evento de exclusão é `DocumentDeleted`, no singular, mas o `data` traz uma **lista** de
> documentos.
>
> O evento `InvoiceClosed` não é configurado por organização: ele usa o webhook de **faturamento da
> instância**, configurado pelo administrador do sistema. Os demais eventos são por organização.
### Campos comuns dos eventos de documento
Todo evento de documento traz, dentro de `data`, as informações básicas do documento:
```json
{
"id": "b12cb1b2-5d6e-40b2-a050-097d068c4c11",
"name": "Contrato de prestação de serviços",
"creationDate": "2026-09-10T13:02:11.482Z",
"updateDate": "2026-09-10T14:27:35.115Z",
"folder": {
"id": "0a9e1f3c-2b44-4f6a-9d0e-77c1b8a4e510",
"name": "Contratos 2026",
"parentId": null,
"organizationName": "Acme S.A."
},
"organization": {
"id": "5f2c9a71-3e8d-4a12-bb90-6d4e2f1c8a33",
"name": "Acme S.A.",
"identifier": "11222333000181",
"owner": null
},
"createdBy": {
"id": "4d961566-9b03-450c-b144-930e0294bac2",
"name": "Integração ERP"
}
}
```
`folder` é `null` quando o documento não está em uma pasta, e `organization` é `null` em documentos
pessoais.
### Campos adicionais por evento
**`DocumentSigned`** acrescenta `signature`, com os dados de quem assinou:
```json
{
"type": "DocumentSigned",
"data": {
"id": "b12cb1b2-5d6e-40b2-a050-097d068c4c11",
"name": "Contrato de prestação de serviços",
"signature": {
"flowActionId": "4bf61c68-eaf1-455f-b4a1-6141554f1dae",
"date": "2026-09-10T14:27:35.115Z",
"userId": "8c3a1d90-77b5-4e2f-9a61-0f5d2b7c4e88",
"name": "John Wick",
"identifier": "81976153069",
"emailAddress": "john.wick@mailinator.com"
}
}
}
```
**`DocumentApproved`** usa a mesma estrutura, no campo `approval`.
**`DocumentRefused`** traz o campo `refusal`, com os mesmos campos mais o motivo informado:
```json
{
"type": "DocumentRefused",
"data": {
"id": "b12cb1b2-5d6e-40b2-a050-097d068c4c11",
"refusal": {
"flowActionId": "4bf61c68-eaf1-455f-b4a1-6141554f1dae",
"date": "2026-09-10T15:11:02.700Z",
"userId": "8c3a1d90-77b5-4e2f-9a61-0f5d2b7c4e88",
"name": "John Wick",
"identifier": "81976153069",
"emailAddress": "john.wick@mailinator.com",
"reason": "Valor divergente na cláusula 4"
}
}
}
```
**`DocumentConcluded`** traz apenas os campos comuns, sem campos adicionais.
**`DocumentCanceled`** acrescenta `canceledBy`, com o usuário ou aplicação que cancelou (`id`, `name` e
`type`), e `reason`.
**`DocumentExpired`** acrescenta `expirationDate` e `expirationDateWithoutTime`, este último no formato
`yyyy-MM-dd`, conveniente para exibição.
**`DocumentsCreated`** traz em `data` a lista `documents`, cada item com os campos comuns:
```json
{
"type": "DocumentsCreated",
"data": {
"documents": [
{ "id": "b12cb1b2-5d6e-40b2-a050-097d068c4c11", "name": "Contrato 1" },
{ "id": "c98a2f14-1d3b-4c77-8e05-2a6b9d1f4e62", "name": "Contrato 2" }
]
}
}
```
**`DocumentDeleted`** traz a lista `documents` e o campo `action`, indicando o que originou a exclusão:
| `action` | Significado |
|---|---|
| `DeletedByUserOrApplication` | Exclusão feita por um usuário ou aplicação |
| `DeletedByOrganization` | Exclusão em massa da organização |
| `DeletedByFolder` | Exclusão de uma pasta que continha os documentos |
> **Observação**
>
> Em `DeletedByOrganization` e `DeletedByFolder` a lista `documents` é limitada a **100 documentos**, mesmo
> que mais documentos tenham sido excluídos.
**`InvoiceClosed`** traz em `data` os campos `id`, `month`, `year`, `value`, `invoiceTotals` (totais por
tipo de transação), `organization`, `billingInformation` e `totalStorageUsed`, este último o
armazenamento total utilizado, em bytes.
## Entrega, retentativas e ordem
Entender esta seção evita a maior parte dos problemas de integração por webhook.
**A entrega é assíncrona.** Cada evento é enviado por um processo em segundo plano, em fila de baixa
prioridade, e não no mesmo instante da ação do usuário. Um pequeno atraso é normal.
**Sua aplicação deve responder 2xx.** Qualquer outro código de status, ou um erro de conexão, marca a
entrega como falha, e ela é **reagendada automaticamente**, com intervalos crescentes entre as tentativas
(por padrão, até 10 tentativas). O Signer registra em log o status e o corpo da resposta de cada falha, e
vale pedir esses registros ao administrador da instância ao investigar um problema.
**Trate entregas repetidas.** Como há retentativas, o mesmo evento pode chegar mais de uma vez. Torne o
processamento **idempotente**, usando a combinação de `type` com o `id` do documento e, quando existir, o
`flowActionId`, para reconhecer um evento já processado.
**Não há garantia de ordem.** Os eventos são entregues por processos independentes: `DocumentSigned` e
`DocumentConcluded` de um mesmo documento podem chegar fora de ordem. Quando a ordem importar, use o
estado atual do documento como fonte da verdade, consultando `GET /api/documents/{id}`.
**Cada webhook é independente.** Se a organização tem mais de um webhook cadastrado, o evento é enviado a
todos eles separadamente, e a falha em um não afeta os demais.
## Boas práticas
* Responda `200` **imediatamente** e processe o evento depois, de forma assíncrona. Endpoints lentos geram
*timeouts* e retentativas desnecessárias.
* Valide o cabeçalho de autenticação em toda requisição recebida.
* Trate o payload como uma **notificação**, não como fonte da verdade. Antes de uma ação sensível, como
liberar um pagamento, confirme o estado do documento pela API.
* Mantenha a URL estável e alcançável. O Signer precisa chegar até ela pela internet ou, em instalações
*on premises*, pela rede onde o servidor está.
* Registre os eventos recebidos, o que ajuda a diagnosticar divergências entre o seu sistema e o Signer.
## Veja também
* [Primeiros passos](https://docs.lacunasoftware.com/articles/signer/get-started)
* [Guia de Integração](https://docs.lacunasoftware.com/articles/signer/integration-guide)
* [Verificar o status de um documento](https://docs.lacunasoftware.com/articles/signer/integration/tracking#check-document)
**Demonstrações e exemplos**
- Handle document webhooks (signed, approved, refused, concluded): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CheckDocumentStatusScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CheckDocumentStatusScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/checkDocumentStatusScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CheckDocumentStatusScenario.php)
- Handle the invoice-closed billing webhook: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/InvoiceWebhookHandlingScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/InvoiceWebhookHandlingScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/InvoiceWebhookHandlingScenario.php)
- Mark an invoice as paid: [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/InvoiceWebhookHandlingScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/InvoiceWebhookHandlingScenario.java) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/InvoiceWebhookHandlingScenario.php)
# [Lacuna Signer - Guia do usuário](https://docs.lacunasoftware.com/articles/signer/user-guide)
> **Observação**
>
> A documentação deste sistema encontra-se em construção. Pedimos desculpas pela inconveniência. Por favor contate-nos caso
> precise de alguma informação que ainda não está documentada.
* [Geração de documentos](https://docs.lacunasoftware.com/articles/signer/user-guide/gen-docs)
* [Administração de sistema](https://docs.lacunasoftware.com/articles/signer/user-guide/sys-admin)
# [Lacuna Signer - Geração de documentos](https://docs.lacunasoftware.com/articles/signer/user-guide/gen-docs)
Para gerar documentos PDF a partir de um modelo no [Signer](https://docs.lacunasoftware.com/articles/signer), siga os passos abaixo.
## Criando o modelo PDF
Antes de gerar documentos no Signer, é necessário ter um modelo PDF. Um modelo consiste em um documento PDF com campos de formulário editáveis e preenchidos com texto, incluindo marcações dos locais onde o texto específico de cada documento gerado será inserido. Nesse exemplo, será usado o software [PhantomPDF](https://www.foxitsoftware.com/pdf-editor/) para criação do formulário PDF, mas o usuário é livre para escolher qualquer software de sua preferência com essa funcionalidade.
### Adicionando campos de formulário ao PDF
Abra um PDF existente no PhantomPDF, navegue para a seção de *formulário* e selecione a opção de *campo de texto*

Com um clique duplo no campo de formulário é possível escolher opções de aparência e formatação, como tamanho e estilo de fonte. Também é possível preencher o campo com um texto padrão. Nesse campo, digite o texto que deve aparecer nos documentos gerados, usando ' e ' para indicar a parte do texto que deve ser substituída pelos dados fornecidos na geração de documentos.

Repita isso para todos os campos que quiser criar e salve o arquivo.

## Gerando documentos no Signer
Na barra lateral do Signer, selecione a opção **Gerar documentos**.

Envie o modelo PDF criado anteriormente e clique em **Continuar**.

Clique em **Baixar modelo CSV** e abra o arquivo baixado no Excel ou em um editor de texto puro.

Cada linha do arquivo CSV representa um documento que será gerado. A coluna **\[filename\]** determina o nome do documento gerado. Caso ela seja deixada em branco, o nome do documento será gerado automaticamente. Cada uma das demais colunas foi gerada automaticamente a partir dos identificadores que aparecem entre chaves nos campos de formulário do modelo enviado. O valor dessas colunas substituirá o identificador correspondente no modelo enviado.
Preencha o arquivo CSV e salve suas alterações.

Envie o arquivo CSV preenchido.

Preencha as demais configurações da mesma maneira que no envio de documentos. É possível determinar a posição da assinatura de cada signatário no modelo.


> **Dica**
>
> É recomendado que as assinaturas sejam posicionadas no momento da geração dos documentos. Caso isso não seja feito, as assinaturas de cada um dos documentos gerados podem ficar em posições diferentes.
Após preencher as opções e o fluxo dos documentos da maneira desejada, clique em **Enviar**. Os documentos serão gerados e os participantes serão notificados.

Após as assinaturas, os documentos gerados e assinados estarão disponíveis para download.

## Gerando documentos com fluxos por documento
A partir da versão 1.29.0, é possível gerar documentos definindo um fluxo específico para cada documento a ser gerado. Para isso, devem ser adicionadas novas colunas no arquivo
CSV enviado ao Signer.
As seções abaixo descrevem os tipos de informações do fluxo que podem ser adicionadas.
### Signatários
Para definir um ou mais signatários, utilize as colunas abaixo adicionando-as ao prefixo `Signer_x.`, onde `x` é o número do signatário:
* Name: o nome do signatário.
* Identifier: o CPF do signatário.
* Email: o email do signatário.
* Phone (opcional): o telefone celular do signatário.
* Step (opcional): ordem do fluxo na qual deve ocorrer esta ação.
* Title (opcional): título do signatário.
* AllowElectronicSignature (opcional): preencha com `true` para permitir que o signatário assine eletronicamente. Caso contrário, preencha com `false` ou deixe em branco.
* ElectronicType (opcional): tipo de autenticação da assinatura eletrônica. Deve ser usada em conjunto com a opção `AllowElectronicSignature`. Os possíveis valores são:
`SMS`, `AuthenticatorApp` e `Selfie`.
* Signature (opcional): define a posição da imagem de assinatura, no formato `TOP_LEFT_X|TOP_LEFT_Y`, `TOP_LEFT_X|TOP_LEFT_Y|PAGE_NUMBER`,
`TOP_LEFT_X|TOP_LEFT_Y|WIDTH|HEIGHT` ou `TOP_LEFT_X|TOP_LEFT_Y|WIDTH|HEIGHT|PAGE_NUMBER`. Exemplo: `100.5|85|2`.
Exemplo de assinatura digital:
```csv
Info1,Info2,Signer_1.Name,Signer_1.Identifier,Signer_1.Email
Value1,Value2,Alice Venture,111.111.111-11,alice@email.com
Value3,Value4,Bob Venture,222.222.222-22,bob@email.com
```
Exemplo de assinatura eletrônica:
```csv
Info1,Info2,Signer_1.Name,Signer_1.Identifier,Signer_1.Email,Signer_1.AllowElectronicSignature
Value1,Value2,Alice Venture,111.111.111-11,alice@email.com,true
Value3,Value4,Bob Venture,222.222.222-22,bob@email.com,true
```
### Aprovadores
Para definir um ou mais aprovadores, utilize as colunas abaixo adicionando-as ao prefixo `Approver_x.`, onde `x` é o número do aprovador:
* Name: o nome do aprovador.
* Identifier: o CPF do aprovador.
* Email: o email do aprovador.
* Step (opcional): ordem do fluxo na qual deve ocorrer esta ação.
Exemplo:
```csv
Info1,Info2,Approver_1.Name,Approver_1.Identifier,Approver_1.Email
Value1,Value2,Alice Venture,111.111.111-11,alice@email.com
Value3,Value4,Bob Venture,222.222.222-22,bob@email.com
```
### Observadores
Para definir um ou mais observadores, utilize as colunas abaixo adicionando-as ao prefixo `Observer_x.`, onde `x` é o número do observador:
* Name: o nome do observador.
* Identifier: o CPF do observador.
* Email: o email do observador.
Exemplo:
```csv
Info1,Info2,Observer_1.Name,Observer_1.Identifier,Observer_1.Email
Value1,Value2,Alice Venture,111.111.111-11,alice@email.com
Value3,Value4,Bob Venture,222.222.222-22,bob@email.com
```
### Emails notificados
Para enviar o documento para uma lista de emails após a conclusão do fluxo, adicione a coluna `NotifiedEmails` separando os emails pelo caractere `|`.
Exemplo:
```csv
Info1,Info2,NotifiedEmails
Value1,Value2,alice@email.com
Value3,Value4,alice@email.com|bob@email.com
```
> **Aviso**
>
> Ao contrário das outras opções, essa coluna só pode ser adicionada uma vez.
### Fluxos combinados
Ainda é possível definir participantes do fluxo que serão compartilhados para todos os documentos, bastando informá-los no campo de participantes da tela
de geração:

Esses participantes serão combinados com os participantes específicos de cada documento. No entanto, não é possível definir que as ações dos participantes
compartilhados ocorram após as ações dos participantes específicos de cada documento.
## Veja também
* [Guia do usuário](https://docs.lacunasoftware.com/articles/signer/user-guide)
# [Administração de sistema - Lacuna Signer](https://docs.lacunasoftware.com/articles/signer/user-guide/sys-admin)
> **Observação**
>
> A documentação deste sistema encontra-se em construção. Pedimos desculpas pela inconveniência. Por favor contate-nos caso
> precise de alguma informação que ainda não está documentada.
* [Adicionando usuário à administração de sistema](https://docs.lacunasoftware.com/articles/signer/user-guide/sys-admin/add-user)
* [Visualizando os documentos do sistema](https://docs.lacunasoftware.com/articles/signer/user-guide/sys-admin/browse-docs)
# [Adicionando usuário à administração de sistema](https://docs.lacunasoftware.com/articles/signer/user-guide/sys-admin/add-user)
> **Observação**
>
> A documentação deste sistema encontra-se em construção. Pedimos desculpas pela inconveniência. Por favor contate-nos caso
> precise de alguma informação que ainda não está documentada.
Para adicionar um usuário com a função de Administrador do Sistema, vá até a página da **Administração**, disponível no menu do usuário.

Selecione **Usuários**.

Clique em **Adicionar**

É de suma importância que na organização, selecione a organização *sys admin*. Selecione o papel **Administrador**.
Selecione o botão **Convidar usuário**.

O usuário foi adicionado com sucesso!
# [Visualizando os documentos do sistema](https://docs.lacunasoftware.com/articles/signer/user-guide/sys-admin/browse-docs)
> **Observação**
>
> A documentação deste sistema encontra-se em construção. Pedimos desculpas pela inconveniência. Por favor contate-nos caso
> precise de alguma informação que ainda não está documentada.
Para obter acesso aos documentos, direcione-se ao menu da administração.

Selecione **Organizações**

Na aba de opções, selecione **Contas Pessoais**. Isto tornará possível visualizar os documentos que cada usuário submeteu.

Digite o nome do usuário que tem interesse em consultar.

Selecione **Documentos**, para visualizar os documentos deste usuário.

É possível então observar todos os documentos que este usuário submeteu ao sistema.

# [Excluindo documentos](https://docs.lacunasoftware.com/articles/signer/user-guide/sys-admin/del-docs)
Para excluir documentos, selecione **Documentos** na aba do menu principal.

Marque a caixa de seleção do documento que deseja excluir.

Surgirão as opções das ações que podem ser tomadas sobre este documento.

Para excluir, selecione a lixeira. Confirme sua ação selecionando **Excluir**.

Conclua a ação, selecione **Concluir**.

O documento foi excluido com sucesso!
# [Histórico de versões do Signer](https://docs.lacunasoftware.com/articles/signer/changelog)
### 2.22.1 (2026-10-06)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.22.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.22.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.22.1.tar.gz)
* Correções de bugs
* [SIG-1924] Corrigida a descoberta de certificados em nuvem que não encontrava certificados paraguaios: identificadores CI, passaporte e RUC passam a ser buscados pelo seu próprio tipo de documento (atualiza também `Lacuna.Pki.BrazilTrustServices` para 1.7.5-beta01)
Atualiza modelo de banco de dados: não
### 2.22.0 (2026-09-10)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.22.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.22.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.22.0.tar.gz)
* Novas funcionalidades
* [SIG-855] Permite o pré-posicionamento da rubrica (assinatura/iniciais) para participantes do tipo Aprovador e Regra de Aprovação, antes restrito a signatários
* [SIG-897] Permite colar uma imagem da área de transferência (Ctrl+V) para preencher a rubrica e a representação visual da assinatura
* [SIG-1912] Permite definir o idioma da notificação de conclusão por destinatário (PT/EN/ES) na lista "enviar documento assinado para"
* Melhorias
* [SIG-1902] Nova flag `disableRefusal` por participante via API, que oculta o botão de recusar e bloqueia o endpoint de recusa para aquele participante
* [SIG-1913] Nova opção de instância para restringir a configuração de Carimbo do Tempo por organização somente a sysadmins, ocultando a seção dos admins de organização
* [SIG-1600] Suprimidos os warnings do Entity Framework com ordenação explícita em consultas paginadas (Take/Skip) e um índice em Invoices, tornando determinística a ordem de processamento dos jobs em background
* Correções de bugs
* [SIG-1928] Corrigida a assinatura em lote com certificado no HSM que travava o diálogo em "preparando documento" e não assinava nada; certificados inválidos (não confiáveis/expirados/revogados) passam a reportar erro por linha em vez de falso sucesso (também no lote com certificado em nuvem)
* [SIG-1391] Corrigida a representação visual do carimbo de autenticação que persistia após voltar ao modo normal na Assinatura Única
* [SIG-1933] Corrigida a obrigatoriedade de dados de faturamento que não bloqueava a criação de documentos em contas pessoais quando a fatura ainda não tinha valor a cobrar (trial/pré-pago); nova opção habilita a checagem também para faturas de valor zero
Atualiza modelo de banco de dados: sim (migrações: `FlowActionDisableRefusal`, `AddInvoiceStatusCloseJobIndex`)
### 2.21.0 (2026-09-01)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.21.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.21.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.21.0.tar.gz)
* Novas funcionalidades
* [SIG-1925] Novo método de autenticação para assinatura eletrônica: WhatsApp + Selfie
* Melhorias
* [SIG-1895] Melhorias de UI/UX na listagem de documentos, incluindo melhor responsividade mobile e chips de signatários inline
* [SIG-1887] Carregamento de temas sob demanda em vez de embutir todos, reduzindo o CSS inicial
* [SIG-1914] Evita a requisição de preferências de notificação a cada renovação de token quando o cookie de idioma já está definido
* [SIG-780] Melhoria de desempenho na listagem de documentos por meio da denormalização do status do signatário em DocumentUser com índice sincronizado
* [SIG-1881] Nova regra de acesso para validação de documentos: login, acesso na organização e acesso ao documento
* [SIG-1927] Melhoria de performance: evita operações desnecessárias ao receber recusa de acesso a documento
* Correções de bugs
* [SIG-1908] Corrigido o identificador do participante que não era persistido/recarregado em fluxo pré-definido da organização
* [SIG-1926] Corrigida a opção "exigir tipo de certificado" que, para e-CNPJ, exibia e validava o campo de identificador como RUC em vez de CNPJ
* [SIG-1930] Corrigido timeout de SQL no job de limpeza de jobs falhados do Hangfire, com timeout de comando configurável e hints de lock que evitam o bloqueio do servidor Hangfire em execução
* [SIG-1491] Tratamento do erro de crédito insuficiente do BirdId (BILLING_LOCK) com mensagem amigável
* Corrigida a lista de certificados em nuvem na tela de assinatura e uma condição de corrida no dropdown de certificados da assinatura rápida
Atualiza modelo de banco de dados: sim (migração: `DenormalizeDocumentUserWithTrigger`)
> **Cuidado**
>
> Esta versão introduz jobs em background relacionados a desempenho, incluindo um backfill único executado na inicialização da aplicação (SIG-780) e novos jobs recorrentes. Logo após o deploy, enquanto o backfill é executado, a instância pode apresentar um aumento temporário de carga no banco de dados e lentidão na listagem de documentos. Isso é esperado e se normaliza assim que o backfill termina.
### 2.20.0 (2026-08-24)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.20.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.20.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.20.0.tar.gz)
* Melhorias
* [SIG-1911] Novo tipo de identificador "Passaporte": aceita qualquer valor alfanumérico (mínimo 6 caracteres), com extração automática do passaporte a partir de certificados do Paraguai e do Equador na assinatura
* [SIG-263] Log de validação de certificado: dialog de validação reformulado em uma árvore recolhível inline e adição de download/cópia do log de validação por signatário em arquivo TXT
* [SIG-1897] Desacoplada a emissão do boleto (Iugu) do fechamento da fatura, de modo que uma indisponibilidade do gateway não deixe mais faturas travadas sem forma de reprocessamento, com novo endpoint para reemitir as faturas do gateway
* [SIG-1901] Melhorado o uso de threads para mitigar thread pool starvation sob carga
* [SIG-1915] Sincronizado o SDK C# (Signer.Client) com a API atual, corrigindo o endpoint de exclusão de usuário da organização e adicionando Document Flows, Contatos e Marks Sessions
* Correções de bugs
* [SIG-1079] Corrigido erro indevido de permissão ao abrir um documento de uma pasta à qual o usuário tem apenas acesso de pasta, em assinaturas com videoconferência habilitada
* [SIG-1896] Corrigida NullReferenceException ao baixar a versão printer-friendly de um documento cancelado sem evidências armazenadas
* [SIG-1916] Corrigido erro no formulário de usuário da edição de fluxo ao atualizar um usuário selecionado pelo autocomplete
Atualiza modelo de banco de dados: não
### 2.19.0 (2026-08-14)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.19.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.19.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.19.0.tar.gz)
* Melhorias
* [SIG-1889] Permite reuso de sessão de assinatura em nuvem por período configurável, possibilitando que uma única autenticação autorize múltiplas assinaturas sem solicitar novo OTP
* [SIG-1821] Melhorias na integração do IdRC: melhor visibilidade e rastreabilidade no manifesto do documento e listagem de fluxo, controle de configuração aprimorado e representação visual melhorada
* [SIG-1886] Adicionados mecanismos de limpeza de storage do Hangfire para evitar acúmulo ilimitado de jobs falhados no banco de dados
* Correções de bugs
* [SIG-1894] Corrigida a exibição de erro JSON bruto ao visualizar documentos recusados em vez do PDF
* [SIG-1869] Corrigido o auto-cadastro de identificador ao editar etapas de fluxo de participante em instâncias com CI (Cédula de Identidad) como tipo de identificador padrão
* [SIG-1882] Corrigidas sessões do IdRC quebrando durante assinaturas concorrentes
* Corrigido preview de marcas de assinatura e campo de erro de validação que não refletiam corretamente o tipo de identificador selecionado
Atualiza modelo de banco de dados: sim (migrações: `IdRCSessionCancellation`, `IdRCSignedFlag`, `CloudSignatureSessions`)
### 2.18.0 (2026-07-30)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.18.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.18.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.18.0.tar.gz)
* Melhorias
* [SIG-1846] Tipos de tags: cadastro dos tipos de tags nas configurações da organização, autocomplete dos tipos ao criar ou editar um documento, obrigatoriedade configurável de tags na criação de documentos e parâmetro de API para sobrescrever essa obrigatoriedade por requisição
* [SIG-1849] Adicionada configuração da organização que torna obrigatória a seleção de pasta na criação de documentos e templates, com sobrescrita por requisição para aplicações
* [SIG-1829] Permite trocar um participante de uma regra de assinatura ou aprovação em documentos já em andamento: é possível remover o participante e colocar outro no lugar
* [SIG-1860] Respeita o Mode de cada tipo de identificador quando múltiplos identificadores por usuário estão habilitados, adiciona a configuração `RequireAtLeastOneIdentifier` e passa a rejeitar participantes duplicados em regras de assinatura e aprovação
* [SIG-632] Adicionados novos tipos de profissionais autenticadores para autenticação de documentos, com nomes de profissões neutros quanto ao gênero
* [SIG-1879] Permite buscar contatos pelo sobrenome
* [SIG-1830] Melhorada a usabilidade do modal "Carregar fluxo de outro documento": modal mais largo, listagem com duas linhas por item exibindo nome, responsável e data do fluxo, e mais espaço para a barra de busca
* Adicionada ferramenta de migração de planos de cobrança
* Correções de bugs
* [SIG-809] Corrigida a assinatura em lote com HSM quando os documentos não estão mais pendentes para o usuário: o diálogo de lote passa a validar as ações disponíveis antes de abrir, fecha quando não há nada a assinar e restaura a interface quando o PIN do HSM é cancelado
* [SIG-1875] Corrigida a opção de recusa ignorada na assinatura embutida e o erro após recusar um documento
* [SIG-1885] Corrigido o erro ao editar o fluxo de um documento cujo participante possuía sessão biométrica
* [SIG-1884] Corrigido o erro ao apagar pastas associadas a templates; os templates agora são desvinculados ou movidos
* [SIG-1883] Corrigida a exibição da chave crua de i18n `common.phone` como label do campo de celular
* Corrigida a falha dos jobs de expiração de documentos enfileirados antes da atualização, causada por mudança na assinatura do job
Atualiza modelo de banco de dados: sim (migração: `NewAuthenticatorTypes`, `RequireFolderOnDocumentCreationToSubscription`)
### 2.17.1 (2026-07-15)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.17.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.17.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.17.1.tar.gz)
* Melhorias
* [SIG-1866] Adicionada configuração (`AllowBasicAndOperatorUsersToManageContacts`) que permite aos perfis Básico e Operador criar, editar e excluir os contatos da organização
* Correções de bugs
* [SIG-1868] Corrigido o erro "HashStream does not support seeking" ao criar documentos com merge de PDFs em storage S3/MinIO
* [SIG-1867] Corrigida a busca de contatos que retornava os contatos da organização errada (ou nenhum) ao criar um fluxo dentro de uma organização
* [SIG-1863] Corrigido o perfil Gerente não conseguir acessar as configurações da pasta (renomear, mover, configurar permissões)
* [SIG-1842] Corrigida a validação indevida do identificador do certificado ao assinar com múltiplos identificadores habilitados quando o usuário não possui identificador definido
* Corrigida a tecla Enter que não disparava a busca nas barras de ferramentas unificadas
Atualiza modelo de banco de dados: não
### 2.17.0 (2026-07-13)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.17.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.17.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.17.0.tar.gz)
* Melhorias
* [SIG-1861] Adicionado campo opcional `timestampEnabled` na criação de documentos via API, permitindo sobrescrever por documento a configuração de carimbo de tempo da organização
* [SIG-1831] Adicionada configuração por organização para habilitar ou restringir o uso da assinatura eletrônica, com precedência da configuração global da instância
* Correções de bugs
* [SIG-1640] Corrigida falha de assinatura causada por PDFs com estrutura de AcroForm nula (atualização do Lacuna.Pki para 2.22.4)
* Corrigido um pequeno problema no carregamento de certificados na tela de assinatura
Atualiza modelo de banco de dados: sim (migração: `ElectronicSignatureConfigForSubscription`, `DocumentTimestampEnabled`)
### 2.16.1 (2026-07-06)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.16.1)
* Melhorias
* [SIG-1478] Bloqueia o download de um documento após ele ter sido recusado, evitando que documentos recusados circulem como se fossem válidos e assinados; as configurações de download foram movidas para uma seção de configuração dedicada `DocumentDownload`
* Correções de bugs
* Corrige a busca de contatos para que nome, e-mail, telefone e identificador sejam comparados com os termos completos
* Oculta o painel de recibos quando a integração NFE.io está desativada
* Corrige a contagem de páginas no método de mesclagem de PDF
Atualiza modelo de banco de dados: não
### 2.16.0 (2026-07-02)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.16.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.16.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.16.0.tar.gz)
* Melhorias
* [SIG-1796] Nova tela de gestão de notas fiscais (NFSe) no módulo de cobrança: listagem administrativa com status (sucesso, em processamento, erro, cancelado), visão detalhada, filtros de busca, coluna de ID da nota, filtro por conta pessoal, reemissão de notas com falha, envio por e-mail e download de PDF/XML
* Correções de bugs
* [SIG-1538] Corrige documentos que não mudavam para o status "Expirado" após o job de processamento atingir o limite de 10 falhas, ficando presos em um estado inconsistente
* [SIG-1814] Corrige a configuração de PDF/A salva no modelo que não era carregada no formulário de edição nem aplicada na geração de documentos a partir do modelo
* [SIG-1854] Corrige o erro de "link inválido" ao iniciar uma assinatura com IdRC pela área pública através de um link recebido por e-mail, SMS ou WhatsApp
Atualiza modelo de banco de dados: não
### 2.15.1 (2026-06-29)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.15.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.15.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.15.1.tar.gz)
* Melhorias
* [SIG-1298] Gera os relatórios da listagem de documentos de forma assíncrona, em jobs de segundo plano para grandes volumes, enviando um link de download por e-mail para evitar timeouts e consumo excessivo de memória
* Correções de bugs
* [SIG-1693] Continuação da correção anterior: corrige a geração de modelos com dados definidos por campos
* [SIG-1844] Restaura comportamentos do cadastro de usuários em instâncias com identificador único
Atualiza modelo de banco de dados: não
### 2.15.0 (2026-06-26)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.15.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.15.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.15.0.tar.gz)
* Melhorias
* [SIG-1787] Adiciona relatório de Termos de Uso listando os usuários que aceitaram os termos, com nome, data/hora e versão aceita, com filtro por versão e envio por e-mail
* [SIG-1816] Melhora a UX/UI da tela de autenticação e assinatura, reorganizando-a em seções mais claras e exibindo as informações do destinatário na etapa de autenticação por código
* Correções de bugs
* [SIG-1693] Corrige erros na API /templates que afetavam regras de aprovação e assinatura, incluindo o tratamento de identificadores e a validação de destinatários
* [SIG-1845] Corrige usuários impedidos de acessar a plataforma após a atualização de seus dados de identificador no GrantID
Atualiza modelo de banco de dados: não
### 2.14.4 (2026-06-15)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.14.4) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.14.4.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.14.4.tar.gz)
* Melhorias
* [SIG-1810] Corrige atribuição do mês da invoice para usar o fuso horário configurado na instância (BRT) em vez de UTC, evitando que documentos criados no fim do último dia do mês sejam cobrados no mês seguinte
* [SIG-1819] Atualiza dependências NuGet para resolver conflitos de build
* Correções de bugs
* [SIG-1813] Corrige toggle de assinatura eletrônica que não atualizava corretamente ao alterar o contexto de segurança
* [SIG-1685] Corrige desalinhamento do filtro de status na listagem de documentos do admin
* [SIG-1506] Corrige truncamento de nomes longos de organização na listagem de documentos
Atualiza modelo de banco de dados: não
### 2.14.3 (2026-06-09)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.14.3) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.14.3.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.14.3.tar.gz)
* Correções de bugs
* [SIG-1808] Corrige cartão de crédito não sendo salvo quando cadastrado como método de pagamento padrão no checkout Iugu
* [SIG-1809] Respeita o idioma de notificação configurado do usuário ao adicionar participantes via busca de contatos
* [SIG-1811] Envia notificação de solicitação de assinatura ao adicionar participante em etapa pendente de documento em andamento
* [SIG-1812] Corrige erro no endpoint de obtenção da URL de assinatura para signatários sem identificadores
Atualiza modelo de banco de dados: não
### 2.14.2 (2026-06-08)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.14.2) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.14.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.14.2.tar.gz)
* Correções de bugs
* Corrigida aplicação de máscara para identificadores exibidos na área privada
* Corrigida busca em TSP para usar os identificadores da tabela de identificadores
Atualiza modelo de banco de dados: não
### 2.14.1 (2026-06-05)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.14.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.14.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.14.1.tar.gz)
* Correções de bugs
* [SIG-1788] Corrigido o registro de certificados para tratamento correto da comparação com os identificadores do usuário
* [SIG-1789] Corrigido identificador apagado ao editar o perfil do usuário na organização em instâncias com múltiplos identificadores
* Corrigida sincronização de CPF com o GrantId em instâncias sem CPF obrigatório
Atualiza modelo de banco de dados: não
### 2.14.0 (2026-06-03)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.14.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.14.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.14.0.tar.gz)
* Melhorias
* [SIG-1709] Conversão de documentos para PDF/A: conversão de uploads para PDF/A na criação de documentos, geração de documentos, assinatura única e modelos (formulários), com seleção de perfil PDF/A e integração com o PdfService
* Correções de bugs
* [SIG-1805] Corrigida violação de chave estrangeira ao remover notificações no NotifyJob
* [SIG-1405] Evitado o envio da versão para impressão por e-mail em fluxos com muitos participantes
* Corrigida assinatura avançada com assinatura eletrônica para usuários sem identificadores
* Corrigido tratamento de aprovadores antigos com tipo de identificador nulo
Atualiza modelo de banco de dados: sim (migração: `PdfA`)
### 2.13.3 (2026-06-01)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.13.3) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.13.3.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.13.3.tar.gz)
* Melhorias
* [SIG-744] Retorno do uso de armazenamento da organização no webhook de fechamento de fatura
* Correções de bugs
* Corrigido autocomplete de usuários não carregando os identificadores do usuário atual
* Corrigida exibição do rótulo de identificadores em documentos criados antes do suporte a múltiplos identificadores
* Corrigido erro ao exibir marcas em documentos assinados quando o tipo de identificador é nulo
* Corrigidos problemas na assinatura eletrônica por câmera no modo embutido (iframe / widget)
Atualiza modelo de banco de dados: não
### 2.13.2 (2026-05-25)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.13.2) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.13.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.13.2.tar.gz)
* Melhorias
* Adicionada configuração para desabilitar o job de sincronização de identificadores em background
* Correções de bugs
* Corrigida normalização do texto de busca ao consultar usuários por identificador
* Corrigido filtro de certificados para bloquear certificados sem as usages NonRepudiation ou DigitalSignature
Atualiza modelo de banco de dados: não
### 2.13.1 (2026-05-22)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.13.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.13.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.13.1.tar.gz)
* Correções de bugs
* Corrigida normalização de identificadores de contatos
Atualiza modelo de banco de dados: não
### 2.13.0 (2026-05-22)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.13.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.13.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.13.0.tar.gz)
* Melhorias
* [SIG-1695] Permite que administradores configurem a ordem de prioridade dos provedores de notificação por canal (SMS e e-mail), com fallback automático em caso de falha
* [SIG-1674] Atualização da integração de Liveness/IdScan para utilizar o RestPkiCore
* Correções de bugs
* [SIG-1718] Corrigido envio de e-mails de notificação sem respeitar a linguagem de preferência do destinatário
* [SIG-1778] Corrigida edição de contatos permanecendo disponível para participantes já bloqueados em documentos em andamento
* [SIG-1496] Corrigido reinício de lembretes e envio de notificação ao editar o e-mail de um participante no fluxo
* [SIG-1646] Corrigida importação de contatos via CSV que rejeitava tipos de identificadores válidos
* Corrigida exibição de identificadores na lista de acesso a pastas
* Corrigidos filtros de certificados por uso em assinaturas em lote
Atualiza modelo de banco de dados: sim (migrações: `ElectronicSignatureBioSessions`, `NotificationProviders`)
### 2.12.1 (2026-05-05)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.12.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.12.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.12.1.tar.gz)
* Correções de bugs
* Corrigida falha no tratamento de pontuação na pesquisa de contatos
* Corrigida falha na remoção de identificadores de usuários
Atualiza modelo de banco de dados: não
### 2.12.0 (2026-04-30)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.12.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.12.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.12.0.tar.gz)
* Melhorias
* [SIG-1646] Gerenciamento de base de contatos externos com pesquisa avançada
* [SIG-1585] Revisão de microinterações e estados (loading, vazio, erro) no layout do dashboard
* Correções de bugs
* [SIG-1610] Corrigir responsividade ao exibir muitos provedores de certificado em nuvem
Atualiza modelo de banco de dados: sim (migração: `SubscriptionContacts`)
### 2.11.1 (2026-04-24)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.11.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.11.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.11.1.tar.gz)
* Melhorias
* Flag para desabilitar a sincronização de identificadores no GrantID
* [SIG-1618] Notificar administradores e registrar evento ao atualizar o termo de uso
* Correções de bugs
* Correções de bugs na criação e edição de usuário
Atualiza modelo de banco de dados: não
### 2.11.0 (2026-04-16)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.11.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.11.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.11.0.tar.gz)
* Melhorias
* [SIG-1570] Suporte a múltiplos identificadores por usuário
Atualiza modelo de banco de dados: sim (migration: `MultipleIdentifiers`)
### 2.10.0 (2026-04-16)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.10.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.10.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.10.0.tar.gz)
* Melhorias
* [SIG-1601] Suporte ao CNPJ alfanumérico
* [SIG-1591] Aprimoramento da busca e listagem de pastas no novo dashboard
Atualiza modelo de banco de dados: não
### 2.9.2 (2026-04-14)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.9.2) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.9.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.9.2.tar.gz)
* Melhorias
* Criação do tema gmc
* Melhorias e correções nas regras de CSP
Atualiza modelo de banco de dados: não
### 2.9.1 (2026-04-09)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.9.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.9.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.9.1.tar.gz)
* Melhorias
* [SIG-1690] Nova configuração para modo monotônico do dashboard
* Correções de bugs
* [SIG-1691] Correção de erro no novo dashboard quando o módulo de faturas está desabilitado
Atualiza modelo de banco de dados: não
### 2.9.0 (2026-04-02)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.9.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.9.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.9.0.tar.gz)
* Melhorias
* [SIG-1669] Opção para incluir QR Code de assinatura no corpo do e-mail de lembrete
* Correções de bugs
* [SIG-869] Registro de notificações em lote nas notificações do usuário
* Correção de erro ao realizar assinatura CAdES com certificado digital no WebPKI
Atualiza modelo de banco de dados: sim (migration: `NotificationDocument`)
### 2.8.1 (2026-03-30)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.8.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.8.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.8.1.tar.gz)
* Correções de bugs
* [SIG-1507] Validação de campo de telefone no formulário de modelo falha em dispositivos móveis
Atualiza modelo de banco de dados: não
### 2.8.0 (2026-03-25)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.8.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.8.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.8.0.tar.gz)
* Melhorias
* [SIG-1586] Redesign visual do dashboard
* [SIG-1627] Integração com o serviço de assinatura do Registro Civil (IdRC)
* Correções de bugs
* [SIG-1576] Atualizar lacuna-facetec-client para a versão 2.4.0
Atualiza modelo de banco de dados: sim (migration: `IdRC`)
### 2.7.0 (2026-03-16)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.7.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.7.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.7.0.tar.gz)
* Melhorias
* [SIG-1612] Opção para solicitar CPF de um dos signatários na validação na área pública
* [SIG-1041] Opção para permitir assinatura em lote na área pública
Atualiza modelo de banco de dados: sim
### 2.6.2 (2026-02-27)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.6.2) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.6.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.6.2.tar.gz)
* Correções de bugs
* [SIG-1641] Perda de acesso a documentos dentro de pastas para usuários autorizados
Atualiza modelo de banco de dados: não
### 2.6.1 (2026-02-24)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.6.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.6.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.6.1.tar.gz)
* Melhorias
* [SIG-1622] Modificar layout da navegação das pastas
* Correções de bugs
* [SIG-1637] Pastas e organizações aparecendo em branco no dashboard
* [SIG-1638] Barra indesejada na janela de posicionamento de assinatura
* [SIG-1639] Documento do tipo escritura ativando fluxo externo sem configurações, impedindo a conclusão do documento
Atualiza modelo de banco de dados: não
### 2.6.0 (2026-02-09)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.6.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.6.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.6.0.tar.gz)
* Melhorias
* [SIG-636] Permitir adicionar número de celular na edição de fluxo
* [SIG-702] Enviar e-mail aos participantes após a exclusão do documento
* [SIG-1582] Adicionar ação “Assinar todos os pendentes para mim”
* [SIG-1613] Bloqueio de uploads públicos
* [SIG-1620] Criação de usuário externo no Signer via API
Atualiza modelo de banco de dados: não
### 2.5.2 (2026-01-27)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.5.2) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.5.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.5.2.tar.gz)
* Melhorias
* [SIG-1604] Remoção de assinaturas existentes na geração de envelopes, com aviso prévio na criação do documento
* [SIG-1608] Ocultar CPF ao utilizar certificado e-CNPJ, conforme configuração de sistema
* Correções de bugs
* [SIG-1609] Ajustes na remoção de JavaScript de PDFs para evitar alterações não intencionais nos PDFs
Atualiza modelo de banco de dados: não
### 2.5.1 (2026-01-22)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.5.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.5.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.5.1.tar.gz)
* Melhorias
* [SIG-1580] Botões no dashboard redirecionam para a página de documentos com o filtro selecionado
Atualiza modelo de banco de dados: não
### 2.5.0 (2026-01-22)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.5.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.5.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.5.0.tar.gz)
* Melhorias
* [SIG-1580] Nova visualização de documentos no dashboard
* [SIG-1530] Revisão do design da janela de solicitação de permissão de acesso à geolocalização
Atualiza modelo de banco de dados: não
### 2.4.11 (2026-01-19)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.4.11) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.4.11.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.4.11.tar.gz)
* Correções de bugs
* [SIG-1541] Erro ao consultar documento em pasta via integração por API
Atualiza modelo de banco de dados: não
### 2.4.10 (2026-01-06)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.4.10) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.4.10.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.4.10.tar.gz)
* Correções de bugs
* [SIG-1574] Continuação do ajuste na submissão de documentos para assinatura, corrigindo cenários adicionais não cobertos na versão 2.4.9
Atualiza modelo de banco de dados: não
### 2.4.9 (2026-01-05)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.4.9)
* Correções de bugs
* [SIG-1574] Submissão de documentos para assinatura não funciona corretamente em alguns casos
Atualiza modelo de banco de dados: não
### 2.4.8 (2025-12-30)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.4.8) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.4.8.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.4.8.tar.gz)
* Melhorias
* [SIG-1548] Mostrar prévia de assinatura na assinatura digital e na Assinatura Única
* [SIG-1550] Adicionar tema *French Blue Pink*
* [SIG-1549] Remover CPF e nome do titular ao utilizar certificado e-CNPJ
* Correções de bugs
* [SIG-1551] FaceTec: endereço IP do usuário não está sendo enviado ao REST PKI
Atualiza modelo de banco de dados: não
### 2.4.7 (2025-12-18)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.4.7) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.4.7.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.4.7.tar.gz)
* Correções de bugs
* [SIG-1547] Erro de *call stack overflow* ao submeter determinados modelos DOCX
Atualiza modelo de banco de dados: não
### 2.4.6 (2025-12-17)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.4.6.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.4.6.tar.gz)
* Correções de bugs
* [SIG-1075] Ativar remoção de Javascript dos PDFs submetidos para criação de documento
Atualiza modelo de banco de dados: não
### 2.4.5 (2025-12-17)
* Correções de bugs
* [SIG-1542] Títulos personalizados da organização não carregam quando a organização já está selecionada no momento de abertura da página de criação do documento
* [SIG-1543] Correção na exclusão de organizações com modelo associado a pasta
* [SIG-1544] Correção no posicionamento de assinatura em fluxos com regra de aprovação
* [SIG-1545] Certificados PKI Brasil não são listados na assinatura quando o usuário não possui CPF
* Melhorias
* [SIG-1546] Diminuir opacidade das marcas d'água
Atualiza modelo de banco de dados: não
### 2.4.4 (2025-12-05)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.4.4.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.4.4.tar.gz)
* Melhorias
* [SIG-1532] Enviar e-mails por SMTP utilizando MailKit
Atualiza modelo de banco de dados: não
### 2.4.3 (2025-11-17)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.4.3) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.4.3.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.4.3.tar.gz)
* Correções de bugs
* [SIG-1537] Ajuste na Assinatura Única para assinatura sem posicionamento da representação visual
* Melhorias
* [SIG-1531] Melhorar visibilidade do "x" para fechar a mensagem de pré-visualização quando assinatura está pré-posicionada
Atualiza modelo de banco de dados: não
### 2.4.2 (2025-11-08)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.4.2) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.4.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.4.2.tar.gz)
* Melhorias
* [SIG-1529] Atualizar lacuna-facetec-client para a versão 2.1.0
* [SIG-1527] Desativar autocomplete para usuários bloqueados
Atualiza modelo de banco de dados: não
### 2.4.1 (2025-11-07)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.4.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.4.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.4.1.tar.gz)
* Correções de bugs
* [SIG-1535] Ajuste de tamanho da fonte da representação visual no front-end
Atualiza modelo de banco de dados: não
### 2.4.0 (2025-10-28)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.4.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.4.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.4.0.tar.gz)
* Melhorias
* [SIG-858] Definir Regras de assinatura para mais participantes
* [SIG-1494] Suporte a envio de notificação em idioma específico
* [SIG-1497] Adicionar evento ao gerar link de compartilhamento
* [SIG-1504] Atualizar biblioteca do Datavalid para 1.4.0
* [SIG-1512] Registrar Porta de Origem para eventos registrados
* [SIG-1525] Permitir desativação completa do validador de documentos
* [SIG-1513] Atualizar FaceTecClient para 2.2.2
* Correções de bugs
* [SIG-873] Título não aparece ao posicionar assinatura
* [SIG-1124] Não está diminuindo a fonte da representação visual no Firefox
* [SIG-1502] Posição replicada em múltiplos PDFs e erro ao adicionar rúbrica na Assinatura Única
* [SIG-1523] Ao editar as posições das assinaturas depois de criado não está sendo salvo corretamente
* [SIG-1524] Toggle de permitir assinatura eletrônica não está funcionando corretamente na edição e na recuperação de fluxo de outro documento
Atualiza modelo de banco de dados: sim
### 2.3.0 (2025-09-25)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.3.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.3.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.3.0.tar.gz)
* Melhorias
* [SIG-1482] Enviar notificação direta ao compartilhar link via WhatsApp
* [SIG-1508] Adição do tema Prussian Blue Green
* [SIG-1509] Permitir exibir rodapé com informações de um único signatário
* [SIG-381] Permitir definição de regras para aprovador na ordem das ações
* [SIG-816] Melhoria na seleção de documentos na tela de documentos
* [SIG-1501] Adicionar opção para buscar um plano na tela de gestão de organização
* [SIG-1505] Revisar mensagem de validação em casos de expiração de certificado digital
* Correções de bugs
* [SIG-1485] Atualizar pacotes com vulnerabilidades
* [SIG-1487] Segmentar envio de notificação de Termo de Uso
* [SIG-1490] Problema na edição do acesso de usuário no SysAdmin
* [SIG-1492] Erro na troca de assinatura avançada para simples
* [SIG-1493] Erro na geração de relatórios formatados no Linux
* [SIG-1498] Testes estão falhando por causa dos e-mails
* [SIG-1499] Troca de cores de tema na submissão de arquivo na assinatura única utilizando Firefox
* [SIG-1510] Assinatura única não está validando certificado no momento da assinatura
Atualiza modelo de banco de dados: sim
### 2.2.1 (2025-08-07)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.2.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.2.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.2.1.tar.gz)
* Correções de bugs
* [SIG-1488] Erro ao selecionar o modelo para criar/gerar documentos
Atualiza modelo do banco de dados: não
### 2.2.0 (2025-08-06)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.2.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.2.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.2.0.tar.gz)
* Melhorias
* [SIG-580] Permitir habilitar/desabilitar ou trocar tipo da assinatura eletrônica na edição de fluxo
* [SIG-1464] Atualização da FaceTec
* [SIG-1466] Página para revisão e assinatura individual de um lote de documentos
* [SIG-1467] Desativação da opção "Recusar-se a assinar"
* [SIG-1475] Permitir desabilitar LTV
* [SIG-1486] Suporte a validação de documentos com novas políticas da ICP Brasil
* Correções de bugs
* [SIG-705] Corrigir permissionamento da API de Eventos
* [SIG-825] Erro de má requisição ao tentar alterar o fluxo
* [SIG-1352] Erro ao editar email de participante de regra de assinatura (SignRule)
* [SIG-1471] Posicionamento de assinatura está obrigatório mesmo quando representação visual está desabilitada
* [SIG-1480] Assinaturas "somem" após posicioná-las em mais de um documento com número de páginas diferentes
* [SIG-1481] Manifesto de Assinaturas desconfigurado ao assinar documento no formato Paisagem
* [SIG-1483] Evidências de assinatura eletrônica aparecem sobrepostas no manifesto
* [SIG-1484] Corrigir violação de CSP ao abrir Hangfire via clique no menu
Atualiza modelo do banco de dados: não
> **Cuidado**
>
> A partir desta versão, a licença do PKI SDK passa a ser validada na inicialização da aplicação e, caso não esteja válida, a inicialização
> será interrompida.
> **Aviso**
>
> Antes de atualizar para esta versão, garanta que sua licença suporta versões do PKI SDK lançadas até 2025-08-01.
### 2.1.1 (2025-06-30)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.1.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.1.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.1.1.tar.gz)
* Correções de bugs
* [SIG-1470] Notificação de código de verificação não está utilizando personalização de e-mail da organização
* [SIG-1472] Barra de rolagem não está visível no upload de documentos
* [SIG-1476] Marca de assinatura não aparece no tipo de assinatura simples
Atualiza modelo do banco de dados: não
### 2.1.0 (2025-06-12)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.1.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.1.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.1.0.tar.gz)
* Melhorias
* [SIG-405] Permitir posicionar assinaturas ao fazer upload de nova versão do documento
* [SIG-1374] Trocar o ícone do botão de "mais" por um ícone de "troca" na tela de criação de modelo
* [SIG-1434] Bloqueio de usuário em instância aberta
* [SIG-1457] Remover o e-mail da tela de assinatura quando a autenticação estiver configurada somente com SMS e WhatsApp (sem e-mail)
* [SIG-1468] Permitir configurar security headers
* [SIG-1469] Otimizar consulta de documentos por participante
* Correções de bugs
* [SIG-1164] Arquivos do envelope recebem marca com chave relativa ao próprio arquivo ao invés do envelope
Atualiza modelo do banco de dados: sim
### 2.0.1 (2025-05-26)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.0.1)
* Correções de bugs
* [SIG-1465] Corrigir botão da home page padrão
Atualiza modelo do banco de dados: não
### 2.0.0 (2025-05-21)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=2.0.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-2.0.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-2.0.0.tar.gz)
* Melhorias
* [SIG-1429] Atualizar versão do SPA
* [SIG-1459] Permitir a definição do tamanho da fonte por signatário, no pré-posicionamento via API
* [SIG-1460] Criar tema oregon blue cadet
* [SIG-1462] Traduzir mensagens de validação de assinaturas
* Correções de bugs
* [SIG-1461] Passos não preenchidos de Assinatura eletrônica não estão bloqueando o envio da requisição
* [SIG-1463] Documentos com caracteres especiais estão sendo enviado com nome attachment no e-mail
* **Mudanças importantes**: antes de atualizar execute os passos descritos na página de [Atualização para versão 2.x](https://docs.lacunasoftware.com/articles/signer/on-premises/update-20).
Atualiza modelo do banco de dados: sim
> **Cuidado**
>
> A partir desta versão foi alterada a versão mínima exigida do SQL Server para o SQL Server 2016 (13.x). Para reverter a compatibilidade para o SQL Server 2014,
> adicione a configuração `SqlServer__CompatibilityLevel` com valor `120`.
> **Aviso**
>
> Antes de atualizar para esta versão, garanta que sua licença suporta versões do PKI SDK lançadas até 2025-05-20.
### 1.80.1 (2025-04-22)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.80.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.80.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.80.1.tar.gz)
* Correções de bugs
* [SIG-1458] Uso elevado do BlobStorage em assinaturas em lote
Atualiza modelo do banco de dados: não
### 1.80.0 (2025-04-15)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.80.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.80.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.80.0.tar.gz)
* Novas funcionalidades
* [SIG-1382] Assinatura com autenticação por selfie e documento de identificação
* Melhorias
* [SIG-1428] Integração da FaceTec com Rest PKI
* [SIG-1454] Permitir desabilitar Swagger
* [SIG-1456] Permitir consultar disponibilidade de usuário no Datavalid por API
* Correções de bugs
* [SIG-1453] Criador do documento não é notificado quando documento é concluído após assinatura em lote
Atualiza modelo do banco de dados: não
### 1.79.0 (2025-03-13)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.79.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.79.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.79.0.tar.gz)
* Melhorias
* [SIG-1162] Relatório de faturas por organizações/contas pessoais
* [SIG-1432] Reduzir o número de etapas para assinar um docmento após uma submissão de formulário de modelo
* [SIG-1433] Exibir uma mensagem informando que o documento está sendo gerado na submissão de formulário de modelo
* [SIG-1436] Pular a tela inicial do processo de assinatura
* [SIG-1439] Criação de Modelos pelo Perfil Básico
* [SIG-1441] Incluir informação de assinatura qualificada na versão para impressão da assinatura única
* Correções de bugs
* [SIG-1351] Permitir que a opção no campo "Múltipla escolha" possa ser desmarcada na tela de "Criar formulário pré-preenchido"
* [SIG-1355] Descrição de campo com tipo Caixa de Seleção não está espaçada corretamente
* [SIG-1356] Permitir remover descrição de um campo de modelo
* [SIG-1385] Mensagem de erro incorreta na API de criar organização
* [SIG-1444] Erro ao usar importação de dados dos participantes e preencher o campo "Enviar documento assinado para"
Atualiza modelo do banco de dados: sim
### 1.78.1 (2025-02-27)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.78.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.78.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.78.1.tar.gz)
* Correções de bugs
* [SIG-1443] Incompatibilidade com SQL Server mais antigo que o 2017
Atualiza modelo do banco de dados: não
### 1.78.0 (2025-02-25)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.78.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.78.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.78.0.tar.gz)
* Novas funcionalidades
* [SIG-1403] Adicionar webhook de exclusão de documentos
* [SIG-1346] Definir títulos pré-estabelecidos por organização
* [SIG-1422] Extração de relatórios em lote de pastas
* Melhorias
* [SIG-831] Usuários confundem tempo de reenvio de mensagem SMS com validade do código SMS
* [SIG-1148] Adicionar os detalhes da ação nos Eventos do documento
* [SIG-1289] Não permitir exclusão de organizações com débito em aberto
* [SIG-1425] Desativar notificação de cancelamento de documento em ordem de assinaturas habilitada
* [SIG-1435] Adicionar controle de número de requisições de consulta de chave de documento
* [SIG-1438] Envio de mensagens de Whatsapp com Zenvia
* Correções de bugs
* [SIG-1344] Falha ao copiar link de compartilhamento e compartilhar diretamente via WhatsApp no iOS
* [SIG-1384] Endereço IPv6 está extrapolando os limites da caixa de evidências
* [SIG-1387] Relatório de usuário de organização está incluído membros antigos
* [SIG-1430] Erro ao criar documento em pasta compartilhada com usuário
* [SIG-1427] E-mail válido está sendo considerado como inválido
Atualiza modelo do banco de dados: não
> **Cuidado**
>
> Esta versão é compatível apenas com o SQL Server 2017 ou mais novo. Se você estiver utilizando uma versão mais antiga do SQL Server, por favor pule esta versão
> e atualize para a versão 1.78.1 ou mais nova.
### 1.77.0 (2025-01-23)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.77.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.77.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.77.0.tar.gz)
* Novas funcionalidades
* [SIG-1365] Configuração por documento para desabilitar envio de notificações
* [SIG-1383] Opção de modelo restrito
* Melhorias
* [SIG-1368] Melhorar visibilidade de erros de envio de notificações
* [SIG-1392] Adicionar fila de prioridade no Hangfire
* [SIG-1407] Atualizar SPA
* [SIG-1421] Atualizar FaceTec engine
* [SIG-1423] Melhorias na criação de documentos a partir de templates por API
* Correções de bugs
* [SIG-1248] Criação de documento com o e-mail invalido
* [SIG-1386] Assinatura eletrônica simples está utilizando o fuso horário GMT 0
* [SIG-1393] Assinatura única não está adicionando o titulo no manifesto
* [SIG-1416] Não está validando número de telefone do Paraguai
* [SIG-1420] Não é possível baixar a versão original de documentos concluídos
Atualiza modelo do banco de dados: sim
> **Cuidado**
>
> Esta versão é compatível apenas com o SQL Server 2017 ou mais novo. Se você estiver utilizando uma versão mais antiga do SQL Server, por favor pule esta versão
> e atualize para a versão 1.78.1 ou mais nova.
> **Aviso**
>
> A partir desta versão, os logs são registrados particionados por data no caso de hospedagem no Azure. Se estiver hospendando a aplicação nesse ambiente, verifique
> se possui a configuração `ASPNETCORE_ENVIRONMENT` definida com valor `Azure` e que a configuração `Serilog__WriteTo__0__Args__keyGenerator` **não está definida**.
### 1.76.0 (2024-12-04)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.76.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.76.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.76.0.tar.gz)
* Novas funcionalidades
* [SIG-1357] Envio de tokens de autenticação via SMSToken
* [SIG-1400] Habilitar funcionamento de Liveness em assinatura embutida (iFrame)
* [SIG-1402] Adicionar configuração de instância para ocultar SubjectName em assinaturas Xades
* Melhorias
* [SIG-1347] Permitir bloqueio de downloads de documentos pendentes
* [SIG-1360] Adicionar botão para gerar relatórios de eventos
* [SIG-1362] Permitir a emissão de relatório de aviso de vencimento dos certificados
* [SIG-1411] Adicionar configuração para definir tempo de timeout de banco de dados
* [SIG-1370] [Modelos] Criar a tag ao clicar no botão "mais" nas opções de "Múltiplas opções", escolha única
* [SIG-1371] [Modelos] Reorganização dos cards na tela de criação de modelo
* [SIG-1372] [Modelos] Trazer o seletor de ordem dos Destinatários para os campos do modelo
* [SIG-1373] [Modelos] Melhorias para a dialog na tela de seleção de modelos
* [SIG-1375] [Modelos] Aumentar o padding nas tags de campos identificados
* [SIG-1376] [Modelos] Criar um dropdown simplificado para seleção dos tipos de assinatura
* [SIG-1377] [Modelos] Substituir os toggles por checkboxes e utilizar os textos sugeridos
* [SIG-1378] [Modelos] Incorporar sugestões do link de compartilhamento
* [SIG-1379] [Modelos] Trocar "Nome" do campo por "Rótulo do campo"
* [SIG-1380] [Modelos] Adicionar as labels "Marcado" e "Desmarcado" aos checkboxes
* Correções de bugs
* [SIG-1308] Ao adicionar telefone a um destinatário o botão de salvar está sempre ativo na tela de edição de modelo
* [SIG-1366] [Modelos] Bug ao substituir valores do template
* [SIG-1399] Erro ao gerar faturas porque o campo do PIX excede os limites
* [SIG-1410] Documento assinado com assinatura simples não possui marcas de assinatura
Atualiza modelo do banco de dados: sim
### 1.75.2 (2024-09-17)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.75.2) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.75.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.75.2.tar.gz)
* Correções de bugs
* [SIG-1364] Não está sendo possível assinar o termo de uso com assinatura eletrônica
Atualiza modelo do banco de dados: não
### 1.75.1 (2024-09-12)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.75.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.75.1.tar.gz)
* Melhorias
* [SIG-1358] Configuração para desabilitar a exibição evidências de assinatura na tela de validação pública
Atualiza modelo do banco de dados: não
### 1.75.0 (2024-09-12)
* Novas funcionalidades
* [SIG-1330] Integração com FaceTec 3d-liveness (apenas para testes internos)
* Melhorias
* [SIG-1035] Configuração para permitir enviar anexos nos emails de documentos pendentes e concluídos
* [SIG-1071] Disponibilizar o link para validação do documento na tela de detalhes do documento
* [SIG-1196] Permitir autenticação unicamente por whatsapp nos fluxos montados pelo CSV
* [SIG-1328] Bloquear uso de câmeras virtuais para tirar a selfie
* Correções de bugs
* [SIG-1340] A revogação de acesso na administração de usuários não está removendo os acessos das pastas dos usuários
* [SIG-1348] Erro ao baixar documentos com caracteres especiais na tela de listagem de documentos
Atualiza modelo do banco de dados: sim
### 1.74.1 (2024-08-08)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.74.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.74.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.74.1.tar.gz)
* Melhorias
* [SIG-1331] Otimizar desempenho da listagem de documentos da organização
* [SIG-1341] Impedir utilização de funções eval() em PDFs abertos com PDF.JS
* Correções de bugs
* [SIG-1342] O Webhook 'DocumentExpired' não consta na documentação do Swagger
Atualiza modelo do banco de dados: sim
### 1.73.0 (2024-07-30)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.73.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.73.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.73.0.tar.gz)
* Melhorias
* [SIG-1316] Permitir que o observador visualize anexos na página de observação externa
* Correções de bugs
* [SIG-1306] Erro ao reiniciar lote de assinaturas para instâncias não permitem assinatura eletrônica
* [SIG-1321] Cobrança não está gerando boleto
Atualiza modelo do banco de dados: não
### 1.72.1 (2024-07-03)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.72.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.72.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.72.1.tar.gz)
* Melhorias
* [SIG-1049] Definir ordem de assinatura pelos números da etapa
* [SIG-1300] Ajustar fuso do job de fechamento de faturas
Atualiza modelo do banco de dados: não
### 1.72.0 (2024-07-03)
* Melhorias
* [SIG-1213] Novas opções de personalização de emails por organização
* [SIG-1276] Salvar nome e CPF de usuários no momento da assinatura eletrônica
* [SIG-1295] Esconder total de paginação de telas com tabelas com quantidade muito grande de itens
* Correções de bugs
* [SIG-1169] Plano padrão não está sendo listado na tela de Gestão de Organização
* [SIG-1283] Link de compartilhamento do Whatsapp sendo enviado de forma incompleta
* [SIG-1285] Erro ao gerar versão para impressão se existirem rubricas em páginas que não existem no documento
* [SIG-1286] Erro ao gerar QR code
* [SIG-1294] Erro na validação de documentos com certificados expirados
* [SIG-1297] Erro ao salvar fluxo de documentos com signatário com autenticação apenas por Whatsapp
Atualiza modelo do banco de dados: não
> **Aviso**
>
> Antes de atualizar para esta versão, garanta que sua licença suporta versões do PKI SDK lançadas até 2024-07-03.
### 1.71.0 (2024-05-24)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.71.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.71.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.71.0.tar.gz)
* Novas funcionalidades
* [SIG-1230] Integração para assinatura com certificado em nuvem via protocolo CSC
* [SIG-1232] Procedimento para regerar fatura da iugu após falha no fechamento da fatura
* Melhorias
* [SIG-1038] Permitir acessar pastas no ambiente da Administração
* [SIG-1193] Atualizar pacotes com vulnerabilidades
* [SIG-1205] Mensagem de aviso de alteração de dados do signatário exibida antes da criação do documento
* [SIG-1207] Permitir adicionar ID da Flow Action no request tag do Datavalid
* [SIG-1221] Adicionar limite de signatários personalizável por instância
* Correções de bugs
* [SIG-1206] Schema do Swagger com nome errado
* [SIG-1241] Assinatura eletrônica com Whatsapp não está bloqueando criação de documento se o telefone celular não for informado
* [SIG-1258] Aplicação perdeu acesso à API de detalhes do documento
* [SIG-1259] Assinatura com certificado em nuvem não registra evento de assinatura
* [SIG-1269] Validação incorreta de telefone ao trocar de método de autenticação
Atualiza modelo do banco de dados: não
### 1.70.0 (2024-04-16)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.70.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.70.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.70.0.tar.gz)
* Novas funcionalidades
* [SIG-1194] Permitir adicionar ID da organização como Request Tag de chamadas à Datavalid
* [SIG-1195] Permitir embutir tela de posicionamento de assinaturas
* [SIG-1177] Relatório de perfil dos usuários
* [SIG-1182] Permitir que a recusa da assinatura não afete o status do documento
* Correções de bugs
* [SIG-1134] Eventos da conta pessoal aparecem com caractere especial no lugar do nome da organização
Atualiza modelo do banco de dados: não
### 1.69.1 (2024-04-02)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.69.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.69.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.69.1.tar.gz)
* Novas funcionalidades
* [SIG-1183] Múltiplas marcas de assinatura na assinatura simples
* [SIG-1185] Permitir personalização de recursos de texto do backend e do frontend
* Melhorias
* [SIG-1189] Permitir que os perfis Básico e Operador possam recusar e/ou cancelar um documento
* Correções de bugs
* [SIG-1190] Assinatura eletrônica não está registrando transação de carimbo de tempo
* [SIG-1191] Documento com tipo de assinatura simples pode ser assinado digitalmente na assinatura em lote
* [SIG-1192] Botão enviar está ficando desabilitado ao adicionar participantes antes de enviar arquivo
Atualiza modelo do banco de dados: não
### 1.68.1 (2024-03-06)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.68.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.68.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.68.1.tar.gz)
* Correções de bugs
* [SIG-1186] Versão para impressão de documento assinado com assinatura única aparece sem signatários
* [SIG-1187] Não está sendo possível assinar o documento com a rubrica preposicionada
Atualiza modelo do banco de dados: não
### 1.68.0 (2024-03-05)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.68.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.68.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.68.0.tar.gz)
* Novas funcionalidades
* [SIG-949] Documento simples - documento composto por assinaturas eletrônicas e que seja validado no ITI
* [SIG-1061] Definir o tipo de assinatura (simples, avançada e qualificada) para o signatário na criação do documento
* [SIG-1176] Configuração para carregamento prévio de certificados com objetivo de carregar LCRs grandes
* Melhorias
* [SIG-1171] Ao remover usuário básico de organização remover também seus acessos em pastas
* [SIG-1174] Relatório de documentos concluídos na Administração
* [SIG-1179] Atualizar método de listagem de pastas no Signer Client
* [SIG-1180] Adicionar tema folly russian violet
* [SIG-1052] Permitir assinar com e-CNPJ em nuvem
Atualiza modelo do banco de dados: sim
### 1.67.0 (2023-12-19)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.67.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.67.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.67.0.tar.gz)
* Novas funcionalidades
* [SIG-1087] Permitir visualização do anexo ao clicar sobre o ícone
* [SIG-1152] Configuração para gerar manifesto de assinaturas ao validar um documento
* Melhorias
* [SIG-1084] Adicionar nas configurações gerais da instância sobre config para criar organizações
* [SIG-1170] Baixa Resolução de PDFs quando são visualizados em dispositivos mobile
* Correções de bugs
* [SIG-1166] Assinatura de termo de uso com certificado em nuvem não funciona
* [SIG-1167] Documento expirado após exclusão
Atualiza modelo do banco de dados: não
### 1.66.0 (2023-11-13)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.66.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.66.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.66.0.tar.gz)
* Novas funcionalidades
* [SIG-1157] Permitir adicionar link de cancelar recebimento nas notificações enviadas por e-mail
* [SIG-1158] Rastreamento de abertura e cliques em e-mails
* [SIG-1161] Suporte a validação por QR Code do validador do ITI
* Melhorias
* [SIG-1151] Melhorar validação de PDFs com anotações
* [SIG-1153] Configuração para notificar participantes quando anexos são adicionados ou excluídos
* [SIG-1154] Configuração para notificar participantes quando dados do documento são atualizados
* [SIG-1155] Configuração para notificar participantes quando nova versão é adicionada
* Correções de bugs
* [SIG-1145] Não é possível atualizar preferências de pagamento à partir do menu Cobranças
* [SIG-1150] Webhook de documento concluído sendo disparado antes do webhook de documento assinado
* [SIG-1156] Não é possível assinar com certificado em nuvem na área pública se usuário estiver logado com usuário diferente do signatário
* [SIG-1160] Créditos da organização não são exibidos quando ela tem plano personalizado
Atualiza modelo do banco de dados: sim
### 1.65.0 (2023-10-11)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.65.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.65.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.65.0.tar.gz)
* Novas funcionalidades
* [SIG-1106] Configuração para definir como obrigatória a seleção de pastas na criação do documento
* [SIG-1139] Permitir adicionar múltiplos webhooks por organização
* Melhorias
* [SIG-1143] Permitir integração com Datavalid usar API v3 e endpoint de biometria apenas facial
* Correções de bugs
* [SIG-1144] Tela de link expirado gera e-mail de ação pendente indepentemente se o documento já está concluído
Atualiza modelo do banco de dados: não
### 1.64.1 (2023-10-02)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.64.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.64.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.64.1.tar.gz)
* Correções de bugs
* [SIG-1140] Email de boas-vindas enviado mais de uma vez aos usuário após assinatura de documentos
Atualiza modelo do banco de dados: não
### 1.64.0 (2023-09-28)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.64.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.64.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.64.0.tar.gz)
* Novas funcionalidades
* [SIG-700] Adicionar novos tipos de representação visual de assinatura
* Melhorias
* [SIG-451] Permitir adicionar/remover usuários da organização via Integração
* [SIG-652] Permitir que administrador da Organização altere os dados de usuários
* [SIG-750] Permitir a criação de documentos em pastas compartilhadas de organizações
* [SIG-868] Exibir botão de recarregar certificados quando nenhum certificado é encontrado para o assinante
* [SIG-987] Listagem de documentos excluídos por API
* [SIG-1137] Erro ao enviar espaço no começo/fim do nome de participante por API
* [SIG-1141] Permitir utilizar qualquer e-mail para participantes existentes ao criar documentos (instâncias com chave e-mail e CPF único)
* Correções de bugs
* [SIG-1140] Documento notarizado é registrado com marca d'agua
Atualiza modelo do banco de dados: sim
> **Aviso**
>
> Antes de atualizar para esta versão, garanta que sua licença suporta versões do PKI SDK lançadas até 2023-09-27.
### 1.63.0 (2023-08-28)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.63.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.63.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.63.0.tar.gz)
* Novas funcionalidades
* [SIG-948] Permitir baixar/exibir o arquivo na validação de documentos externos P7S
* Melhorias
* [SIG-514 Melhorar descrição da permissão de Enviar Documento na tela de adicionar usuário em organização
* [SIG-532] Adicionar eventos de visualização/download de documentos
* [SIG-830] Incluir o tipo de autenticação realizada da assinatura eletrônica no relatório gerado na Organização
* [SIG-983] Indicar as iniciais do usuário no posicionamento da rubrica
* [SIG-984] Configuração para alterar nome e CNPJ da organização somente para o sys admin
* [SIG-1042] Permitir bloquear o uso da conta pessoal
* [SIG-1093] Definir por organização a posição das marcas na versão assinada
* [SIG-1128] Alterar a opção de autenticação "e-mail e selfie (Datavalid)" para "e-mail e biometria facial"
* Correções de bugs
* [SIG-911] Permitir cadastrar aprovador sem a opção de rubricar
* [SIG-966] Manifesto exibe assinaturas inválidas como válidas
* [SIG-1060] Ao editar um fluxo e posicionar a rubrica obrigatória não está sendo salva a alteração
* [SIG-1116] Usuário com faturamento desabilitado ainda gerou fatura
* [SIG-1132] Não é possível receber código de autenticação se e-mail do participante estiver errado e for trocado após solicitação de envio
Atualiza modelo do banco de dados: não
### 1.62.0 (2023-08-10)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.62.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.62.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.62.0.tar.gz)
* Novas funcionalidades
* [SIG-1085] Adição de campo para pesquisar por documentos em Eventos e tela de eventos da Organização
* [SIG-1121] Ao posicionar assinatura confirmar o posicionamento por meio de um pop-up
* Melhorias
* [SIG-747] Perfil intermediário entre Básico e Gerente na Organização
* [SIG-872] Usuários básico não deve visualizar faturas ou informações administrativas da organização
* [SIG-1091] Enviar anexos no Gerar Documentos
* [SIG-1125] Melhorar busca de políticas de assinatura ICP Brasil na validação de documentos
* Correções de bugs
* [SIG-1133] Rubrica dos usuários aparece com string "undefined"
Atualiza modelo do banco de dados: não
### 1.61.1 (2023-08-04)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.61.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.61.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.61.1.tar.gz)
* Correções de bugs
* [SIG-1130] Não está sendo possível editar o email de um participante no fluxo.
Atualiza modelo do banco de dados: não
### 1.61.0 (2023-08-02)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.61.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.61.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.61.0.tar.gz)
* Melhorias
* [SIG-974] Permitir adicionar participantes no fluxo após o documento concluído
* [SIG-1037] Considerar envelope como documento para fins de cobrança se não existir um valor definido para envelopes
* [SIG-1120] Salvar conteúdo das notificações no Storage ao invés do banco de dados
* [SIG-1127] Fazer com que configuração NotifyPendingActionsIndividually funcione também para aprovadores
* Correções de bugs
* [SIG-996] Lembrete de assinatura não funciona para ações adicionadas na edição de fluxo
* [SIG-1126] Erro ao exibir detalhes de documento que tem anexo enviado por usuário que não participa do fluxo do documento
Atualiza modelo do banco de dados: não
> **Aviso**
>
> Antes de atualizar para esta versão, garanta que sua licença suporta versões do PKI SDK lançadas até 2023-08-01.
> Esta versão marca o início da migração do conteúdo das notificações do banco de dados para o Blob Storage configurado.
### 1.60.0 (2023-07-17)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.60.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.60.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.60.0.tar.gz)
* Novas funcionalidades
* [SIG-1112] Exibir versão original no documento para a assinatura quando o Manifesto Simplificado estiver ativado
* Melhorias
* [SIG-1051] Adicionar informação sobre envelope no relatório gerado da organização
* [SIG-1117] Diminuir impacto ao sistema quando um usuário é adicionado em organização com muitos documentos
* [SIG-1119] Alterar o tamanho máximo do identifier dos usuários
* [SIG-1068] Descritivo das permissões dos perfis nas pastas
* Correções de bugs
* [SIG-1113] Erro ao assinar em uma assinatura embutida com rubrica pré posicionada
* [SIG-1115] Ao utilizar NotifyParticipantsDefaultValue em assinatura eletrônica, o checkbox está vindo selecionado
* [SIG-1118] Quando um documento é excluído, os lembretes cadastrados não são removidos.
Atualiza modelo do banco de dados: sim
### 1.59.0 (2023-07-03)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.59.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.59.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.59.0.tar.gz)
* Melhorias
* [SIG-1092] Adicionar tema djp (deep-jungle-princeton)
* [SIG-1096] Trocar autenticação com Zoom para Server to Server ao invés de JWT
* Correções de bugs
* [SIG-1067] Edição de fluxo não exibe telefone do participante
* [SIG-1103] Adicionar a possibilidade de usar um fuso horário diferente do IdentifierHandler
* [SIG-1105] No Dashboard a visualização da quantidade de documentos pendentes está incluindo os recusados
* [SIG-1110] PDF assinado com Validation Stamp apresenta erros na validação do Adobe Reader
Atualiza modelo do banco de dados: não
> **Aviso**
>
> Antes de atualizar para esta versão, garanta que sua licença suporta versões do PKI SDK lançadas até 2023-06-28.
> Se você utiliza a integração com o Zoom, configure as credenciais Server to Server antes de atualizar.
### 1.58.3 (2023-06-19)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.58.3) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.58.3.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.58.3.tar.gz)
* Melhorias
* [SIG-1102] Aderir a novas políticas de mensagens do Whatsapp
* [SIG-1108] Enviar informação de bairro na criação de notas fiscais com NFE.io
* Correções de bugs
* [SIG-1104] Notificação para configurar senha é enviada mesmo se a instância possui login apenas com AD
Atualiza modelo do banco de dados: não
### 1.58.2 (2023-05-30)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.58.2) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.58.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.58.2.tar.gz)
* Correções de bugs
* [SIG-1097] Não é possível assinar na assinatura única
Atualiza modelo do banco de dados: não
### 1.58.1 (2023-05-25)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.58.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.58.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.58.1.tar.gz)
* Melhorias
* [SIG-1094] Adição do tema dim palatinate blue
Atualiza modelo do banco de dados: não
### 1.58.0 (2023-05-25)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.58.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.58.0.tar.gz)
* Novas funcionalidades
* [SIG-1073] Suporte à Cédula de Identidad do Paraguai
* [SIG-1090] Permitir adição de marca personalizada no Validation Stamp
* Melhorias
* [SIG-986] Criação de relatórios de Organizações e Cobrança na administração
* [SIG-1045] Incluir relatório por período nas pastas
* Correções de bugs
* [SIG-1088] Documento editado com assinatura eletrônica não exibe erros na assinatura
* [SIG-1095] Ao selecionar todos documentos na assinatura em lote com HSM somente os primeiros 10 são assinados
Atualiza modelo do banco de dados: não
> **Aviso**
>
> Antes de atualizar para esta versão, garanta que sua licença suporta versões do PKI SDK lançadas até 2023-05-25.
### 1.57.0 (2023-04-24)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.57.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.57.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.57.0.tar.gz)
* Novas funcionalidades
* [SIG-1028] Desativar/ativar carimbo de tempo por organização
* [SIG-1056] Permitir exclusão de assinatura na plataforma via API para CADES
* Melhorias
* [SIG-1031] Melhorar mensagem quando assinatura eletrônica não está habilitada e usuário tenta assinar
* [SIG-1063] Código de validação do documento via API
* [SIG-1086] Permitir recuperação do arquivo original enviado quando estiver configurado Validation Stamp
* Correções de bugs
* [SIG-197] Assinatura utilizando certificados HSM não aplica a imagem do carimbo nem o texto correto na representação visual
* [SIG-1081] Posicionamento de assinaturas não está sendo salvo no Gerar Documentos
Atualiza modelo do banco de dados: sim
### 1.56.0 (2023-03-27)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.56.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.56.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.56.0.tar.gz)
* Novas funcionalidades
* [SIG-350] Notificação de assinaturas para Whatsapp
* [SIG-1062] Criar tema Zomp Ut Orange
* Melhorias
* [SIG-1065] Exibir notificações de SMS e Whatsapp junto com notificações de e-mail
* Correções de bugs
* [SIG-1064] Corrigir ordem em que métodos de autenticação são solicitados na tela de assinatura eletrônica
Atualiza modelo do banco de dados: sim
### 1.55.0 (2023-03-03)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.55.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.55.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.55.0.tar.gz)
* Melhorias
* [SIG-798] Enviar múltiplos documentos com fluxo específico por documento
* [SIG-1044] Adicionar novos provedores de SMS (Amazon SNS e Tigo)
* Correções de bugs
* [SIG-870] Opção posicionar texto no meio da imagem não funciona para configuração de imagem de assinatura no perfil do usuário
* [SIG-1015] Erro ao assinar documentos em lote com certificado
* [SIG-1039] Não está sendo possível definir o plano padrão para organização/usuário que tenha um plano personalizado
* [SIG-1047] Quando desativado a representação visual na organização, ela ainda aparece na assinatura eletrônica e em nuvem
Atualiza modelo do banco de dados: não
### 1.54.0 (2023-02-17)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.54.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.54.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.54.0.tar.gz)
* Novas funcionalidades
* [SIG-1032] Push notification para assinatura/aprovação pendente no app Web PKI
* [SIG-1040] Configuração para habilitar/desabilitar representação visual por organização
* Melhorias
* [SIG-1022] Permitir definição do padrão de assinatura (CAdES, XAdES ou PAdES) em tela
* [SIG-1034] Configuração para definir hora limite para assinatura do documento
* Correções de bugs
* [SIG-1043] Não é possível assinar com certificado em nuvem se já existir um certificado expirado
Atualiza modelo do banco de dados: sim
### 1.53.0 (2023-02-02)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.53.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.53.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.53.0.tar.gz)
* Novas funcionalidades
* [SIG-819] Novo sistema de cobrança
* [SIG-1007] Configuração para definir representação visual por organização
* [SIG-1033] Link direto para contratação de plano
* Melhorias
* [SIG-947] Permitir especificar em tela que a assinatura só pode ser realizada com certificado PF/PJ
* [SIG-1000] Adicionar configurações básicas da instância na administração
* [SIG-1036] Suporte a brand do Web PKI
* Correções de bugs
* [SIG-834] Visualização de lista de todas organização nas configurações da pasta
* [SIG-958] Assinatura única (rápida) com certificado em nuvem não gera marcas de ValidationStamp nem manifesto simplificado
Atualiza modelo do banco de dados: sim
### 1.52.1 (2023-01-18)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.52.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.52.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.52.1.tar.gz)
* Novas funcionalidades
* [SIG-978] Criar tema Silver Eerie Black
* Melhorias
* [SIG-43] Refatorar a atualização dos acessos aos documentos e pastas
Atualiza modelo do banco de dados: não
### 1.52.0 (2023-01-10)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.52.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.52.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.52.0.tar.gz)
* Novas funcionalidades
* [SIG-743] Adicionar configuração deixar pré-posicionamento das assinaturas do signatário como padrão na criação de documentos
* [SIG-1021] Relatório de documentos PDF e CSV
* [SIG-1025] Assinatura eletrônica com certificado PFX
* Melhorias
* [SIG-994] Atualização de framework para .NET 6
* [SIG-1023] Adicionar papéis padrão ao definir papel de signatário
Atualiza modelo do banco de dados: sim
### 1.51.0 (2022-12-08)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.51.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.51.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.51.0.tar.gz)
* Novas funcionalidades
* [SIG-978] Criar tema yale blue yellow
* Melhorias
* [SIG-999] Permitir administrador da pasta criar subpastas
* [SIG-1006] Otimizar geração de relatórios da organização
* Correções de bugs
* [SIG-1005] Não é possível preencher dados de um PDF form pela API de criação de documento se não for definido um tipo para o documento
* [SIG-1002] Integração com HSM não funciona no linux
* [SIG-1009] Erro ao cancelar lote de assinatura
Atualiza modelo do banco de dados: não
### 1.50.0 (2022-11-16)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.50.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.50.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.50.0.tar.gz)
* Novas funcionalidades
* [SIG-445] Adicionar tags ao documento para consulta
* [SIG-1001] Integração com Google Tag Manager
* Melhorias
* [SIG-988] Configuração para esconder cards de Participantes e Ordem das Ações na tela de detalhes de documento para usuários sem permissão de Admin ou Gerente.
* [SIG-992] Webhook para criação de documento
* Correções de bugs
* [SIG-964] Erro ao enviar arquivo com nome grande demais
* [SIG-1002] Assinatura em lote de todos documentos não funciona com HSM
Atualiza modelo do banco de dados: sim
### 1.49.2 (2022-11-01)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.49.2) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.49.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.49.2.tar.gz)
* Melhorias
* [SIG-998] Adicionar configuração para carregamento de menu por meio de iframe
* Correções de bugs
* [SIG-995] Erro ao assinar um documento que o fluxo foi alterado
Atualiza modelo do banco de dados: não
### 1.49.1 (2022-11-01)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.49.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.49.1.tar.gz)
* Melhorias
* [SIG-982] Permitir importação de documentos concluídos entre instâncias
* Correções de bugs
* [SIG-965] Não está sendo possivel adicionar usuario em pasta
Atualiza modelo do banco de dados: não
### 1.49.0 (2022-10-18)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.49.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.49.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.49.0.tar.gz)
* Melhorias
* [SIG-975] Não exibir botão para fazer login abaixo do ícone após assinar um documento se opção de esconder links na área pública estiver habilitada
* [SIG-969] Permitir que se um usuário de regra de assinatura recusar o documento os demais signatário ainda possam assinar
* [SIG-944] Otimizar uso de memória
* Correções de bugs
* [SIG-991] Rate limiting da iugu ao tentar criar faturas na virada do mês
* [SIG-989] Múltiplas notificações enviadas para participante de organização que desabilitou envio de lembretes periódicos
* [SIG-970] Opção "Voltar para tela inicial" da tela de acesso restrito não respeita tela inicial personalizada
Atualiza modelo do banco de dados: sim
### 1.48.1 (2022-09-15)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.48.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.48.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.48.1.tar.gz)
* Melhorias
* [SIG-967] Criar configuração para não fazer discovery de certificado em nuvem
* Correções de bugs
* [SIG-968] Busca na tela de usuários retorna múltiplas opções se tiver número no filtro
Atualiza modelo do banco de dados: não
### 1.48.0 (2022-09-06)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.48.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.48.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.48.0.tar.gz)
* Novas funcionalidades
* [SIG-938] Envio automático de lembretes de assinatura para documentos pendentes
* Melhorias
* [SIG-515] Permitir acessar tela de detalhes do documento ao clicar em evento relacionado a ele
* [SIG-65] Melhorar tratamento de erros da assinatura em lote
* Correções de bugs
* [SIG-963] Resultado de validação não é definido ao validar arquivos XML
* [SIG-956] Opção Versão para impressão escrita errada no download em lote de documentos
* [SIG-923] Webhook de recusa não é enviado em regra de assinatura
* [SIG-811] Mensagem de erro ao tentar criar documento com fluxo composto apenas de observadores
Atualiza modelo do banco de dados: sim
### 1.47.0 (2022-08-18)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.47.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.47.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.47.0.tar.gz)
* Novas funcionalidades
* [SIG-915] Configuração para nome do remetente do email vir de acordo com a organização
* [SIG-832] Integração com GOV BR para assinatura
* Melhorias
* [SIG-953] Configuração para definir o tempo para enviar o email de boas vindas
* [SIG-941] Ao compartilhar link para assinar eletronicamente não exigir login
* [SIG-931] Permitir política AD-RC em assinatura no XML completo
* [SIG-758] Configuração para mascarar email
* Correções de bugs
* [SIG-952] Assinar documento PDF com opção forceCadesSignature exibe tarja azul
* [SIG-951] Erro ao gerar versão para impressão de documento PDF CADeS
Atualiza modelo do banco de dados: sim
### 1.46.1 (2022-08-01)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.46.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.46.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.46.1.tar.gz)
* Correções de bugs
* [SIG-946] Erro ao assinar com certificado em nuvem.
Atualiza modelo do banco de dados: não
### 1.46.0 (2022-07-29)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.46.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.46.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.46.0.tar.gz)
* Novas funcionalidades
* [SIG-943] Criação e validação de documentos de saúde (prescrição, atestado etc)
* [SIG-939] Configuração para não permitir assinar digitalmente se assinatura eletrônica for habilitada para um assinante
* [SIG-932] Exibir número de ações e participantes que já concluiram ações na tela de assinatura
* [SIG-738] Adicionar aprovadores no Manifesto de assinaturas
* Melhorias
* [SIG-928] Adicionar novas colunas ao relatório de documentos da organização
* [SIG-914] Configuração para habilitar a visualização do campo Título como padrão
* [SIG-913] Inserir opões de tamanho de página maiores na listagem de documentos
* [SIG-779] Adicionar o criador do documento no relatório de transações de uma fatura
* Correções de bugs
* [SIG-942] Está sendo possível colocar múltiplas marcas de assinatura para um mesmo documento
* [SIG-940] Assinatura não é posicionada corretamente ao carregar fluxo de documento existente
* [SIG-935] Erro ao editar fluxo de documento que possui observadores por API
Atualiza modelo do banco de dados: não
### 1.45.0 (2022-06-30)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.45.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.45.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.45.0.tar.gz)
* Novas funcionalidades
* [SIG-904] Permitir especificar que assinatura só pode ser realizada com certificado PF
* [SIG-726] Permitir excluir certificados na tela de certificados pessoais
* Melhorias
* [SIG-933] Atualizar Swagger
* [SIG-920] Inserir informação de assinatura com certificado digital ICP-Brasil no Manifesto
* [SIG-905] Adicionar evento de edição de plano padrão da instância
* [SIG-885] Melhorar exibição de dados da assinatura de carimbadora
* Correções de bugs
* [SIG-924] Botão de visualizar documentos não aparece na notificação de documentos concluídos
* [SIG-907] Chamadas repetidas ao assinar documento
* [SIG-818] Adicionar loading ao solicitar reenvio de notificação
* [SIG-817] Erro ao exibir informações de signatários sem CPF
Atualiza modelo do banco de dados: sim
### 1.44.1 (2022-06-15)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.44.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.44.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.44.1.tar.gz)
* Melhorias
* [SIG-917] Melhorias na assinatura embutida
* [SIG-912] Melhorar desempenho da consulta de usuários com GrantID
* [SIG-875] Criar tema chartreuse traditional violet
* Correções de bugs
* [SIG-925] Erro ao carregar swagger no Linux
* [SIG-922] Assinatura embutida sem preview com rubrica pré-cadastrada exibe erro de rubrica não cadastrada
* [SIG-918] Erro ao gerar Manifesto
* [SIG-908] Erro ao baixar versão para impressão
Atualiza modelo do banco de dados: não
### 1.44.0 (2022-06-03)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.44.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.44.0.tar.gz)
* Novas funcionalidades
* [SIG-898] Adequar o sistema para inserção de chat com script personalizado
* [SIG-875] Criar tema vivid sky blue
* Melhorias
* [SIG-902] Adicionar um “tooltip” sobre a data na tela de Documentos
* Correções de bugs
* [SIG-906] Erro ao buscar rubrica de signatário de regra de assinatura na assinatura externa
* [SIG-888] Alterar o tipo de assinatura na ordem das ações de acordo com a assinatura realizada
* [SIG-812] Não é possível mudar página do documento no iPhone
Atualiza modelo do banco de dados: não
### 1.43.3 (2022-05-19)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.43.3.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.43.3.tar.gz)
* Correções de bugs
* [SIG-896] Usuário redirecionado para página de login após assinatura embutida
* [SIG-895] Contagem incorreta de documentos na tela de detalhes da organização
* [SIG-892] Instância sem representação visual exibe configuração de Rubrica na criação de documentos
* Melhorias
* [SIG-899] Permitir província especial para usuários estrangeiros na cedula de identidade e RUC do equador
Atualiza modelo do banco de dados: não
### 1.43.2 (2022-05-09)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.43.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.43.2.tar.gz)
* Correções de bugs
* [SIG-893] Assinaturas ADRA e ADRC inválidas por conta de LCR com formatação errada publicada pelo ITI
Atualiza modelo do banco de dados: não
### 1.43.1 (2022-05-04)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.43.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.43.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.43.1.tar.gz)
* Novas funcionalidades
* [SIG-887] Permitir forçar política de assinatura de PDFs para padrão ICP Brasil
* Melhorias
* [SIG-882] Não normalizar PDF se ele for PDF/A
* Correções de bugs
* [SIG-886] Não é possível baixar versão para impressão de documento
Atualiza modelo do banco de dados: não
### 1.43.0 (2022-04-29)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.43.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.43.0.tar.gz)
* Novas funcionalidades
* [SIG-619] Permitir importação de documentos assinados em outros sistemas
* [SIG-138] Assinatura com rubrica na Assinatura normal e na Assinatura rápida
* [SIG-793] Permitir rubricar página em posição diferente das demais
* [SIG-783] Permitir definir modo de rubrica (desabilitada, opcional ou obrigatória) por participante
* Melhorias
* [SIG-883] Nova forma de configurar carimbadora com Basic Authentication
* [SIG-877] Configuração para habilitar o "Pendente para mim" como padrão para todos os usuários
* [SIG-874] Possibilitar editar tipo de imagem de rubrica pré-posicionada
* [SIG-871] Configuração para definir número máximo de de Organizações e Pastas na filtragem de documentos
* [SIG-863] Adicionar rubrica padrão com iniciais do nome do usuário
* [SIG-857] Atualizar PKI SDK
* [SIG-850] Diminuir impacto da validação de documentos com muitas assinaturas
* [SIG-846] Mockar chamadas à iugu nos testes
* Correções de bugs
* [SIG-878] Pagina em branco no manifesto de assinatura de documentos que não são PDF
Atualiza modelo do banco de dados: sim
> **Aviso**
>
> Antes de atualizar para esta versão, garanta que sua licença suporta versões do PKI SDK lançadas após 2022-04-29.
### 1.42.0 (2022-04-11)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.42.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.42.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.42.0.tar.gz)
* Novas funcionalidades
* [SIG-859] Definir título do signatário na Assinatura Única
* Melhorias
* [SIG-845] Especificar o tipo de assinatura na ordem das ações
* Correções de bugs
* [SIG-864] Link de validação de documento com caracteres adicionais
* [SIG-844] Documento cancelado aparece como pendente na listagem de pastas
Atualiza modelo do banco de dados: não
### 1.41.0 (2022-03-20)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.41.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.41.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.41.0.tar.gz)
* Melhorias
* [SIG-835] Melhorias nas restrições de uploads
* [SIG-787] Permitir configuração de replyto automático para o criador do documento
* [SIG-509] Permitir definir que os primeiros sete dias de um plano será de trial (gratuito)
* Correções de bugs
* [SIG-851] Não é possível enviar documento com aplicação Sys Admin
* [SIG-849] Assinatura eletrônica consta como digital na Etiqueta de Assinatura
* [SIG-847] Não é possível baixar documentos do envelope se Manifesto simplificado estiver habilitado
Atualiza modelo do banco de dados: sim
### 1.40.0 (2022-03-20)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.40.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.40.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.40.0.tar.gz)
* Melhorias
* [SIG-308] Melhorar caso de uso de usuário que precisa assinar documento duas (ou mais) vezes com papéis diferentes na mesma etapa
* Correções de bugs
* [SIG-829] Arquivo PDF/A perde a atribuição após assinatura
Atualiza modelo do banco de dados: não
### 1.39.1 (2022-03-20)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.39.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.39.1.tar.gz)
* Melhorias
* [SIG-822] Adicionar tema eminence-cornflower-blue
* [SIG-821] Adicionar campo "disable pending notifications" a API de nova versão de documento
Atualiza modelo do banco de dados: não
### 1.39.0 (2021-12-13)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.39.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.39.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.39.0.tar.gz)
* Novas funcionalidades
* [SIG-722] Permitir definir posicionamento das marcas da versão para impressão por organização
* [SIG-730] Permitir definir configurações do email por organização
* Melhorias
* [SIG-729] Opção para deixar o "Notificar participantes" da assinatura em lote desmarcado por padrão
* [SIG-800] Melhorar nome do arquivo P7S assinado
* [SIG-802] Adicionar tema electric purple indigo
* [SIG-804] Alterar texto dos botões de notificações de assinatura/aprovação para "Ver documento"
* [SIG-810] Adicionar informações de exigências de certificado no flow action model
* Correções de bugs
* [SIG-794] Corrigir informações no relatório da fatura da organização
* [SIG-807] Botão de criar documentos continua aparecendo após desativar permissão de criação de documentos por padrão
* [SIG-808] Corrigir validação da cédula de identidade do Ecuador
Atualiza modelo do banco de dados: sim
### 1.38.0 (2021-11-11)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.38.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.38.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.38.0.tar.gz)
* Novas funcionalidades
* [SIG-723] Permitir solicitar acesso aos administradores no sistema fechado
* [SIG-665] Limite mensal por tipo de transação
* Melhorias
* [SIG-452] Exibir CPF e permitir pesquisar por ele na consulta de Usuários
* Correções de bugs
* [SIG-796] Erro ao exibir página de assinatura se as Marcas tiverem largura/altura 0
* [SIG-792] SMS Genérico está enviando content-type errado
* [SIG-791] Não é possível alterar método de pagamento padrão na Administração do sistema
* [SIG-781] Assinatura com diversos documentos falha no primeiro documento na Assinatura Única
Atualiza modelo do banco de dados: não
### 1.37.0 (2021-10-21)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.37.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.37.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.37.0.tar.gz)
* Novas funcionalidades
* [SIG-774] Permitir configurar métodos de pagamento disponíveis na integração com a iugu
* [SIG-773] Adicionar o nome do documento no assunto do email de notificação
* [SIG-772] Exibir signatários pendentes na listagem de documentos
* [SIG-229] Redimensionar representação visual da assinatura ao posicionar
* Melhorias
* [SIG-784] Permitir fazer upload do arquivo ao invés de digitalizar no Scanner
* [SIG-764] Permitir personalizar assuntos de emails por instância
* [SIG-759] Aba "Recusados" para documento
* Correções de bugs
* [SIG-778] Erro ao salvar dados de faturamento se o CEP é incompleto
* [SIG-761] PDF que foi assinado e protegido com senha não exibe mensagem adequada no momento da assinatura
Atualiza modelo do banco de dados: não
### 1.36.0 (2021-10-06)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.36.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.36.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.36.0.tar.gz)
* Novas funcionalidades
* [SIG-769] Rota para redirecionar para o cadastro
* [SIG-751] Configuração para anexar o documento original no e-mail de assinatura pendente
* [SIG-641] Permitir subir nova versão de um documento
* Melhorias
* [SIG-777] Correções no conteúdo dos emails
* [SIG-776] Criar tema blue venetian red
* [SIG-762] Adicionar suporte ao Provedor de SMS Genérico
* [SIG-757] Adicionar saudação e melhorias nos emails
* [SIG-450] Refatoração dos temas aproveitados do SPA pelo signer
* Correções de bugs
* [SIG-775] Opção de download da versão assinada está aparecendo na validação de documentos cancelados
* [SIG-768] Marca na lateral direita da versão para impressão não aparece em documentos horizontais
* [SIG-767] Inclusão indevida do mixin lacuna-spa
* [SIG-763] Documento com assinatura eletronica de timestamp e certificado auto assinado não exibe todas assinaturas no manifesto
* [SIG-741] Tipo de assinatura eletrônica selfie não aparece corretamente na edição de fluxo
* [SIG-469] Corrigir fluxo de invite
Atualiza modelo do banco de dados: não
### 1.35.0 (2021-09-15)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.35.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.35.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.35.0.tar.gz)
* Novas funcionalidades
* [SIG-752] Permitir definir por instância qual o versão do documento é enviada na notificação de conclusão
* [SIG-576] Seletor de pastas que permita navegar na hierarquia de subpastas
* Melhorias
* [SIG-745] Adicionar informação do tipo de assinatura (eletronica ou digital) nos relatórios de documentos
* [SIG-699] Configuração para mascarar CPFs
* [SIG-687] Assinatura de documento com requisitos de certificado
* Correções de bugs
* [SIG-753] Posicionamento de assinatura não está funcionando na geração de documentos
* [SIG-749] Instância com versão para impressão desabilitada está enviando-a na conclusão do documento
* [SIG-733] Criador de documento da organização não consegue editar fluxo e excluir documentos que criou
Atualiza modelo do banco de dados: sim
### 1.34.0 (2021-08-20)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.34.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.34.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.34.0.tar.gz)
* Novas funcionalidades
* [SIG-740] Adicionar configuração que permita notificar ações pendentes nas assinaturas em lote
* [SIG-737] Criar tema space cadet yellow
* [SIG-181] Permitir configurar imagem de assinatura padrão - assinatura digital e eletronica
* Melhorias
* [SIG-719] Permitir reduzir ações que podem ser tomadas na área pública
* [SIG-689] Tratar cabeçalhos repetidos na geração de documentos
* [SIG-662] Assinatura com certificado em nuvem não exibe erros de falha de validação do certificado na assinatura única
* Correções de bugs
* [SIG-742] Versão para impressão mostra marca d'água de documento expirado quando documento está concluído
* [SIG-734] Orientação incorreta da selfie ao tirar pelo iPhone
* [SIG-725] Erro ao tentar apagar fluxo do documento após o envio de SMS ou Pix
* [SIG-706] Organização não é desbloqueada após o pagamento de faturas pendentes
Atualiza modelo do banco de dados: não
### 1.33.1 (2021-07-30)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.33.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.33.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.33.1.tar.gz)
* Melhorias
* [SIG-736] Atualizar biblioteca de gerenciamento de blobs do Azure
* Correções de bugs
* [SIG-735] Erro ao assinar PDF eletronicamente
Atualiza modelo do banco de dados: não
### 1.33.0 (2021-07-22)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.33.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.33.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.33.0.tar.gz)
* Novas funcionalidades
* [SIG-418] Exibir Histórico de alterações nos detalhes do documento
* [SIG-422] Submissão de Envelopes (assinatura/aprovação em lote externa)
* [SIG-480] Envio de lembretes em lote na tela de documentos
* [SIG-524] Relatório de documento por pasta
* [SIG-596] Criação de Fluxos de Documento
* [SIG-618] Recusa e Cancelamento de documentos
* [SIG-686] Adicionar informações do e-cnpj na representação visual da assinatura
* [SIG-696] Busca avançada de documentos
* [SIG-697] Prazo para assinatura de documentos
* [SIG-713] Personalização dos templates de email por instância
* Melhorias
* [SIG-633] Simplificação do ticket de assinatura para ter URLs menores
* [SIG-703] Permitir esconder nome do provider do certificado em nuvem
* [SIG-712] Adicionar tema international orange apricot
* [SIG-720] Permitir alterar data de expiração de um documento
* [SIG-724] Adicionar tema generic viridian blue
* Correções de bugs
* [SIG-701] Tirar Selfie não funciona no iOS
Atualiza modelo do banco de dados: sim
### 1.32.0 (2021-06-25)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.32.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.32.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.32.0.tar.gz)
* Novas funcionalidades
* [SIG-670] Assinatura eletrônica com PIX
* [SIG-631] Assinatura eletrônica com biometria facial validada pelo SERPRO
* [SIG-538] Recusar assinatura de um documento
* Melhorias
* [SIG-653] Assinatura embutida sem pré-visualização do documento
* Correções de bugs
* [SIG-690] Erro ao assinar documento excluído caso a opção de permitir validação de documentos excluídos esteja habilitada
Atualiza modelo do banco de dados: sim
### 1.31.1 (2021-06-08)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.31.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.31.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.31.1.tar.gz)
* Correções de bugs
* [SIG-691] Botão de compartilhar link de aprovação deve levar à área privada
Atualiza modelo do banco de dados: não
### 1.31.0 (2021-06-08)
* Novas funcionalidades
* [SIG-688] Criar temas international orange green e onyx carrot orange
* [SIG-664] Relatório de todos documentos da instância por mês
* [SIG-655] Permitir gerar link para assinatura do documento
* Melhorias
* [SIG-685] Suportar tipos alternativos de autenticação com AWS S3
* [SIG-656] Suportar validação de assinaturas com algoritmos obsoletos
* [SIG-650] Permitir visualizar arquivos não PDF ao assinar documentos
* [SIG-648] Melhorar tratamento de erros de PDF com senhas
* [SIG-628] Adicionar observação que anexos não são assinados
* [SIG-560] Suportar arquivos com mais de 100 assinaturas
* Correções de bugs
* [SIG-649] Não exibir opção para posicionamento de assinatura para documentos não PDF
Atualiza modelo do banco de dados: não
### 1.30.0 (2021-05-25)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.30.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.30.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.30.0.tar.gz)
* Melhorias
* [SIG-659] Tentar obter carimbo automaticamente após falha ao contatar a carimbadora
* [SIG-646] Adicionar HTML dinâmico da home page como confiável
* [SIG-629] Assinatura de lotes documentos sem precisar selecioná-los página por página
* Correções de bugs
* [SIG-661] Notificação de conclusão não está sendo enviada na assinatura única
* [SIG-658] Emails a serem notificados na conclusão não são enviados quando os documentos são assinados em lote
* [SIG-657] Na geração de documentos vários email são enviados aos participantes
* [SIG-654] Erro ao editar posicionamento do texto da marca de assinatura caso a imagem já tenha sido enviada
* [SIG-645] Exibição de signatários é diferente se o arquivo não é PDF
Atualiza modelo do banco de dados: sim
### 1.29.0 (2021-04-27)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.29.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.29.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.29.0.tar.gz)
* Novas funcionalidades
* [SIG-638] Integração para realização de videoconferências com Zoom
* [SIG-624] Envio de documentos ao final do fluxo
* [SIG-543] CSV com um fluxo para geração de documentos em lote
* Melhorias
* [SIG-460] Exibir assinaturas pendentes no manifesto de assinaturas
* Correções de bugs
* [SIG-643] Opção de configuração de Rubrica não é exibida se autenticação não estiver visível
* [SIG-642] Extensões equivocadas adicionadas no download em lote de documentos
Atualiza modelo do banco de dados: sim
### 1.28.0 (2021-04-06)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.28.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.28.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.28.0.tar.gz)
* Novas funcionalidades
* [SIG-555] Tornar escolha da posição de assinatura obrigatória
* [SIG-476] Remoção de Consentimento
* Melhorias
* [SIG-634] Permitir autenticação com carimbadora por basic authentication
* [SIG-621] Convidar usuários apenas após assinatura do documento
* [SIG-613] Permitir usuário informar número de celular para assinatura eletronica com SMS
* [SIG-578] Adicionar novos provedores de SMS
* Correções de bugs
* [SIG-635] Webhook de invoice não inclui informação de Owner
* [SIG-630] Botão de editar fluxo aparece mesmo para fluxos concluídos
* [SIG-470] Erro ao assinar com representação visual personalizada com arquivo .webp
Atualiza modelo do banco de dados: sim
### 1.27.0 (2021-03-25)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.27.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.27.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.27.0.tar.gz)
* Novas funcionalidades
* [SIG-614] Geração de nota fiscal de serviço (NFS-e)
* [SIG-549] Integração com Gateway de Pagamento iugu:
* [SIG-588] Criar página de checkout para pagamento
* [SIG-593] Opção para permitir o usuário ver e alterar seu método de pagamento padrão
* [SIG-592] Enviar emails aos clientes referentes às faturas
* [SIG-590] Apresentação das informações de pagamento na tela da fatura
* Melhorias
* [SIG-625] Adicionar nova API para integração com a SECURITI
* [SIG-620] Corrigir testes que estão falhando em desenvolvimento
* [SIG-606] Adicionar configuração para remover botão ver documentos em notificações de mais de um documento
* Correções de bugs
* [SIG-626] Corrigir horário exibido na versão para impressão
* [SIG-617] Detecção incorreta de habilitação do Scanner
Atualiza modelo do banco de dados: sim
### 1.26.1 (2021-02-18)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.26.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.26.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.26.1.tar.gz)
* Correções de bugs
* [SIG-616] Nome do arquivo é obrigatório ao fazer upload por API
* [SIG-615] Erro ao enviar documento para usuário com email que é parte de outros emails
* [SIG-609] Corrigir tradução do recurso common.search (i18n)
Atualiza modelo do banco de dados: não
### 1.26.0 (2021-02-11)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.26.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.26.0.tar.gz)
* Melhorias
* [SIG-605] Atualizar Sink do New Relic para melhorar registro de exceções
* [SIG-604] Atualizar Lacuna SPA
Atualiza modelo do banco de dados: não
### 1.25.0 (2021-02-03)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.25.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.25.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.25.0.tar.gz)
* Melhorias
* [SIG-602] - Melhoria no Suporte a AWS S3
Atualiza modelo do banco de dados: sim
### 1.24.3 (2021-01-28)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.24.3.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.24.3.tar.gz)
* Melhorias
* [SIG-599] - Não enviar email de convite se o documento for criado desativando as notificações
* Correções de bugs
* [SIG-601] - Não é possível usar regras de assinatura e anexos privados
Atualiza modelo do banco de dados: não
### 1.24.2 (2021-01-18)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.24.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.24.2.tar.gz)
* Correções de bugs
* [SIG-598] - Erro ao visualizar tela de criação de documento sem estar em nenhuma organização no modo Fechado
Atualiza modelo do banco de dados: não
### 1.24.1 (2021-01-15)
* Correções de bugs
* [SIG-597] - Tematização incorreta da mensagem de posicionamento de assinatura
Atualiza modelo do banco de dados: não
### 1.24.0 (2021-01-14)
* Novas funcionalidades
* [SIG-463] - Exibir assinatura eletrônica como primeira opção
* [SIG-594] - Validação de assinatura eletrônica CAdES e XAdES
* Melhorias
* [SIG-441] - Permitir definir qual página deve ser exibida ao carregar PDF com Widget de assinatura
* [SIG-474] - Adicionar consentimentos em tela para LGPD
* [SIG-496] - Melhorar tratamento de PDFs com senha na assinatura rápida
* [SIG-566] - Email de conclusão de fluxo deveria redirecionar para a área pública
* [SIG-584] - Simplificar tela de assinatura externa
* Correções de bugs
* [SIG-568] - Instrução de posicionamento continua aparecendo após assinatura
* [SIG-595] - Erro ao excluir aprovador no final do fluxo
Atualiza modelo do banco de dados: sim
### 1.23.0 (2021-01-07)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.23.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.23.0.tar.gz)
* Novas funcionalidades
* [SIG-430] - Assinatura eletronica com Selfie
* [SIG-554] - Permitir adicionar anexos a documentos
* Melhorias
* [SIG-408] - Permitir posicionar assinatura na edição de fluxo
* [SIG-583] - Atualizar PKI SDK para corrigir posicionamento de textos
* Correções de bugs
* [SIG-582] - Corrigir validação de RUC (Ecuador)
Atualiza modelo do banco de dados: sim
### 1.22.0 (2020-12-17)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.22.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.22.0.tar.gz)
* Novas funcionalidades
* [SIG-144] - Exibir evidências de assinatura eletronica
* [SIG-551] - Adicionar campo para observações do documento
* [SIG-541] - Permitir definir mensagem a ser adicionada nos emails de assinatura/aprovação pendente
* Melhorias
* [SIG-330] - Permitir definir posição de assinatura de múltiplos arquivos
* [SIG-390] - Exibir todas assinaturas no pré-posicionamento de assinaturas
* [SIG-425] - Validar Hash do Arquivo Scanner no Validador
* [SIG-426] - Marca lateral no documento scanneado
* [SIG-531] - Melhorar tratamento de erro na assinatura de HSM
* [SIG-545] - API para validar assinaturas por ID do documento e incluir evidências da assinatura eletrônica
* [SIG-550] - Aumentar número de pastas recuperadas na tela de criação de documento
* [SIG-559] - Atualizar Lacuna PKI para permitir certificados da ANF (Equador)
* [SIG-570] - Corrigir internacionalização do email de boas vindas e termos de uso
* [SIG-571] - Permitir configurar moeda a ser utilizada no sistema
* [SIG-572] - Permitir definir tipos de arquivo aceitáveis na criação de documentos
Atualiza modelo do banco de dados: sim
### 1.21.0 (2020-12-03)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.21.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.21.0.tar.gz)
* Novas funcionalidades
* [SIG-217] - Remover obrigatoriedade de CPF
Atualiza modelo do banco de dados: sim
### 1.20.0 (2020-11-27)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.20.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.20.0.tar.gz)
* Novas funcionalidades
* [SIG-477] - Integração com Securiti para automatização de solicitações da LGPD
* [SIG-486] - Cobrança por Carimbo de tempo e SMS
* Melhorias
* [SIG-508] - Permitir desabilitar troca de email no momento da submissão do documento
* [SIG-553] - Inserir Titulo do Signatário no Manifesto Simplificado
* [SIG-563] - Permitir criar organizações via integração
* [SIG-565] - Desabilitar indexação da página de validação de documentos
Atualiza modelo do banco de dados: não
### 1.19.0 (2020-11-18)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.19.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.19.0.tar.gz)
* Melhorias
* [SIG-468] - Permitir criação de Subpastas
* [SIG-544] - Identificar assinaturas eletronicas no manifesto e na validação por código
* [SIG-548] - Direcionar usuário para tela de cobrança caso tenha sido verificado que as informações de faturamento não foram preenchidas
* [SIG-557] - Trocar nome do botão inicial de aprovação para revisar
* Correções de bugs
* [SIG-546] - Documento aparece como assinado caso outro participante tenha recusado
* [SIG-547] - Erro na assinatura de arquivo XLSX
* [SIG-558] - Não normalizar PDFs caso ele tenha vindo do Scanner ou possua assinaturas
Atualiza modelo do banco de dados: sim
### 1.18.0 (2020-11-05)
* Novas funcionalidades
* [SIG-483] - Bloqueio de uso enquanto informações de faturamento não estiverem preenchidas
* [SIG-513] - Desabilitar posicionamento automático de representação visual
* [SIG-519] - Webhook após conclusão de etapa
* Melhorias
* [SIG-528] - Permitir validação de documento mesmo excluído
Atualiza modelo do banco de dados: não
### 1.17.0 (2020-10-28)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.17.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.17.0.tar.gz)
* Melhorias
* [SIG-437] - Permitir editar fluxo mesmo com Manifesto Simplificado habilitado
* [SIG-506] - Adicionar logo na marca lateral (validation stamp)
* [SIG-517] - Alterações no manifesto e manifesto simplificado
Atualiza modelo do banco de dados: não
### 1.16.1 (2020-10-23)
* Melhorias
* [SIG-534] - Criar perfil de administração de faturamento
Atualiza modelo do banco de dados: não
### 1.16.0 (2020-10-22)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.16.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.16.0.tar.gz)
* Melhorias
* [SIG-484] - Envio dos dados de faturamento no Webhook de fechamento de fatura
* [SIG-485] - Atualização de status de fatura por API
* [SIG-525] - Melhorar mensagem de erro quando upload não é encontrado
* Correções de bugs
* [SIG-511] - Botão de digitalização fica sobre o botão de excluir documento na tela de criação de documento
* [SIG-526] - Erro na assinatura rápida de documentos não PDF
Atualiza modelo do banco de dados: não
### 1.15.1 (2020-10-21)
📦 Downloads: [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.15.1.tar.gz)
* Correções de bugs
* [SIG-529] - Tela de acesso restrito não aparece se os perfis básicos estiverem configurados
Atualiza modelo do banco de dados: não
### 1.15.0 (2020-10-08)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.15.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.15.0.tar.gz)
* Novas funcionalidades
* [SIG-520] - API para download direto das versões do documento
* [SIG-521] - API simplificada de Upload
* [SIG-522] - Configurar tamanho máximo de upload
* Melhorias
* [SIG-471] - Alterar frase marca d'água da versão sem validade jurídica
Atualiza modelo do banco de dados: não
### 1.14.0 (2020-09-30)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.14.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.14.0.tar.gz)
* Melhorias
* [SIG-427] - Notificar criador do documento na conclusão
* Correções de bugs
* [SIG-499] - Tela de validação apresenta email do cadastro ao invés de email validado na assinatura eletrônica
* [SIG-505] - Ordem das etapas perdidas ao usar fluxo de outro documento
* [SIG-512] - Erro na exibição de status de documento na administração de documentos
Atualiza modelo do banco de dados: não
### 1.13.0 (2020-09-23)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.13.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.13.0.tar.gz)
* Melhorias
* [SIG-366] - Permitir reprovar na ação de aprovação
* [SIG-383] - Melhorar preview de certificados
* [SIG-411] - Melhorar mensagem de erro quando o PDF tem senha e permitir assinatura de PDFs protegidos
* [SIG-438] - Restricted autocomplete por padrão
* Correções de bugs
* [SIG-442] - Não é possível validar arquivo XML
* [SIG-457] - Botão de faturas aparece mesmo com módulo de cobrança desabilitado
* [SIG-459] - Tratar caso em que o pageNumber da representação visual está em uma página que não existe no PDF
* [SIG-479] - Título do signatário não está aparecendo na representação visual da assinatura eletrônica
Atualiza modelo do banco de dados: sim
### 1.12.0 (2020-09-14)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.12.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.12.0.tar.gz)
* Novas funcionalidades
* [SIG-462] - Adicionar perfis para habilitar/restringir criação de documentos e organizações
* [SIG-464] - Permitir enviar telefone celular de participante ao submeter documento por API
* Melhorias
* [SIG-373] - Gerar widget com Typescript
* [SIG-465] - Certificado em nuvem na assinatura rápida
* Correções de bugs
* [SIG-466] - Erro ao editar fluxo com ordem
Atualiza modelo do banco de dados: não
### 1.11.0 (2020-09-02)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.11.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.11.0.tar.gz)
* Melhorias
* [SIG-397] - Integração com Certificados em nuvem
Atualiza modelo do banco de dados: não
### 1.10.4 (2020-08-31)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.10.4.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.10.4.tar.gz)
* Novas funcionalidades
* [SIG-455] - Implementar tema persian-plum-sand
* Melhorias
* [SIG-417] - Adicionar upload segmentado nos uploads de arquivos
Atualiza modelo do banco de dados: não
### 1.10.3 (2020-08-26)
* Novas funcionalidades
* [SIG-429] - Inserir texto indicativo para Baixar APP QRCode
* [SIG-439] - Alterar frases do Manifesto e da versão sem validade jurídica
Atualiza modelo do banco de dados: não
### 1.10.2 (2020-08-25)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.10.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.10.2.tar.gz)
* Novas funcionalidades
* [SIG-449] - Criar tema viridian green yellow
* Correções de bugs
* [SIG-447] - Erro ao enviar SMS com a TotalVoice
* [SIG-448] - Timeout no processo de login
Atualiza modelo do banco de dados: não
### 1.10.1 (2020-08-21)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.10.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.10.1.tar.gz)
* Novas funcionalidades
* [SIG-435] - Criar tema oxford-blue-green
* [SIG-436] - Criar tema castleton-green-orange
* Correções de bugs
* [SIG-420] - Tela bloqueada após tentativa de assinatura com certificado A3 que não está conectado na máquina
Atualiza modelo do banco de dados: não
### 1.10.0 (2020-08-19)
* Melhorias
* [SIG-382] - Associar Aplicações a pastas
* [SIG-384] - Linguagem padrão para o sistema
* [SIG-389] - Ao adicionar um usuário em organização, verificar se o usuário já está cadastrado
* [SIG-399] - Ajustar limite máximo de envio de documento por email
* [SIG-400] - Carregar tela de assinatura rápida com upload já realizado
* [SIG-404] - Adicionar manifesto simplificado juntamente com Validation Stamp
* [SIG-409] - Melhorar mensagem de erro quando o email já está sendo utilizado em outra conta
* [SIG-412] - Suporte a solicitações de exames na geração de documentos
* Correções de bugs
* [SIG-407] - Validation Stamp não aparece em documento com tamanho menor que A4
* [SIG-416] - Erro ao submeter arquivo que não é PDF com o Validation Stamp habilitado
Atualiza modelo do banco de dados: sim
### 1.9.1 (2020-07-23)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.9.1) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.9.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.9.1.tar.gz)
* Correções de bugs
* [SIG-396] - String não traduzida nas configurações de pasta
* [SIG-410] - Scroll desnecessário na home page
Atualiza modelo do banco de dados: não
### 1.9.0 (2020-07-23)
📦 Downloads: [Docker Hub](https://hub.docker.com/r/lacunasoftware/signer/tags?name=1.9.0) · [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.9.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.9.0.tar.gz)
* Novas funcionalidades
* [SIG-378] - Procedimento para recuperação de documentos excluídos
* Melhorias
* [SIG-338] - Melhorar descrição de informações sobre assinatura eletrônica
* [SIG-394] - Mostrar os participantes das etapas do fluxo em ordem alfabética
* [SIG-395] - Reposicionar powered by do footer
* Correções de bugs
* [SIG-403] - Assinatura eletronica está adicionando representação visual mesmo com representação visual desabilitada
Atualiza modelo do banco de dados: não
### 1.8.0 (2020-07-16)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.8.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.8.0.tar.gz)
* Novas funcionalidades
* [SIG-364] - Tipos de documentos para receitas médicas
* Melhorias
* [SIG-337] - Trocar nome do botão assinatura rápida
* [SIG-362] - Criar lista de contato pessoal (Adequação LGPD)
* [SIG-365] - Assinatura eletronica com carimbo de tempo
* Correções de bugs
* [SIG-385] - Verificar se o título dos signatários está funcionando quando o fluxo possui regras de assinatura
* [SIG-391] - Não é possível adicionar observadores na edição do fluxo
* [SIG-392] - Corrigir título da página de termos de uso
Atualiza modelo do banco de dados: sim
### 1.7.0 (2020-07-02)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.7.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.7.0.tar.gz)
* Correções de bugs
* [SIG-370] - Parâmetro ticket sendo exibido na documentação da API de upload
* [SIG-379] - Corrigir criação de organizações sem CNPJ
* [SIG-380] - Corrigir criação de escrituras
* Melhorias
* [SIG-316] - Assinatura em lote com certificados em HSM
* [SIG-317] - Adequação para Ecuador
* [SIG-361] - Permitir configurar tipos de documentos permitidos/exibidos
* [SIG-376] - Permitir listar documentos a partir de API Key
Atualiza modelo do banco de dados: não
### 1.6.2 (2020-06-25)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.6.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.6.2.tar.gz)
* Correções de bugs
* [SIG-377] - Erro ao abrir detalhes de documento com observador que não está no fluxo
Atualiza modelo do banco de dados: não
### 1.6.1 (2020-06-24)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.6.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.6.1.tar.gz)
* Melhorias
* [SIG-374] - Adicionar tema metallic-seaweed-emerald (MSE)
Atualiza modelo do banco de dados: não
### 1.6.0 (2020-06-24)
* Novas funcionalidades
* [SIG-349] - Carregar tela de criação de documento com upload já realizado
* [SIG-360] - Integração com página de Assinatura (Assinatura embutida)
* Melhorias
* [SIG-227] - Simplificar exemplos no Swagger
* [SIG-320] - Melhorar tratamento das marcas de assinatura para documentos com muitos assinantes
* [SIG-327] - Alterar para o singular mensagem de upload de validação
* [SIG-334] - Adicionar documentação das APIs de validação de documento no Swagger
* [SIG-335] - Integração com MoviDesk e JivoChat
* [SIG-339] - Permitir configurar padrão da opção assinaturas ao mesmo tempo
* Correções de bugs
* [SIG-307] - Corrigir testes no DevOps
* [SIG-352] - Erro ao consultar eventos de um usuário
* [SIG-356] - Erro ao submeter documento se assinatura eletronica estiver desabilitada e isAllowedByDefault for true
* [SIG-357] - Timeout ao buscar documento com muitos participantes
* [SIG-358] - Faltando traduções no email de fluxo editado
* [SIG-359] - Assinatura eletronica em lote está exigindo Web PKI instalado
Atualiza modelo do banco de dados: sim
### 1.5.0 (2020-06-15)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.5.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.5.0.tar.gz)
* Novas funcionalidades
* [SIG-331] - Enviar email para todos participantes do fluxo após a conclusão do documento
* [SIG-343] - Assinatura eletronica em lote
* [SIG-344] - Permitir configurar se a assinatura eletronica aparece marcada por padrão
* Melhorias
* [SIG-311] - Obrigatoriedade de enviar informação de geolocalização
* [SIG-318] - Permitir que o participante tenha acesso ao documento após assinar/aprovar na área externa
* [SIG-326] - Testes automatizados de documento escaneado pelo Scanner
* [SIG-332] - Adicionar nome da aplicação nas propriedades do PDF
* [SIG-341] - Esconder versão para impressão
* Correções de bugs
* [SIG-347] - Corrigir traduções na página de validação de documentos
Atualiza modelo do banco de dados: sim
### 1.4.1 (2020-06-08)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.4.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.4.1.tar.gz)
* Correções de bugs
* [SIG-313] - Internacionalização errada (en) no email de aprovar e assinar
Atualiza modelo do banco de dados: não
### 1.4.0 (2020-06-08)
* Novas funcionalidades
* [SIG-216] - Permitir configuração de instância dos detalhes da Versão para impressão
* Melhorias
* [SIG-249] - Internacionalizar notificações
* [SIG-285] - Remover digitalização rápida e alterar comportamento da digitalização
* [SIG-294] - Enviar email para configurar senha ao cadastrar novos usuários
* [SIG-312] - Adicionada configuração para definir se o texto da representação visual deve começar no meio do retângulo
* [SIG-314] - Melhorar configuração de integração com Scanner
* [SIG-323] - Ativar download em lote
* [SIG-329] - Adicionar mais eventos
* Correções de bugs
* [SIG-313] - Status de documento na listagem de pastas está "Pendente" quando está concluído
Atualiza modelo do banco de dados: sim
### 1.3.2 (2020-05-15)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.3.2.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.3.2.tar.gz)
* Correções de bugs
* [SIG-310] - Exceções lançadas ao logar caracteres especiais gerados pelo PKI SDK
Atualiza modelo do banco de dados: não
### 1.3.1 (2020-05-05)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.3.1.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.3.1.tar.gz)
* Correções de bugs
* [SIG-309] - Esconder opção de download em lote até atualização do Web PKI
Atualiza modelo do banco de dados: não
### 1.3.0 (2020-05-05)
* Novas funcionalidades
* [SIG-178] - Download em lote
* [SIG-187] - Permitir selecionar Padrão de assinatura Cades ou Pades na submissão por API
* [SIG-190] - Permitir administradores visualizaram documentos de usuários/organizações
* [SIG-287] - Permitir que aplicações possam pegar link para ação externa
* Melhorias
* [SIG-161] - Permitir alterar dados do usuário na administração
* [SIG-193] - Adicionar links para PDF de documentação do usuário
* [SIG-262] - Melhorias na funcionalidade de Autenticação
* [SIG-293] - Permitir assinar o termo de adesão com assinatura eletronica
* [SIG-297] - Adicionar listagem de documentos na administração
* [SIG-301] - Ajustes de responsividade e remoção de scrolls quando loader é exibido
* [SIG-302] - Validar Assinaturas PDF feitas no Adobe
* [SIG-305] - Tratar erro PinValidationFailed do Amplia
* Correções de bugs
* [SIG-304] - Pedidos de certificado que não podem ser emitidos em Cloud deveriam ser ignorados
* [SIG-306] - Corrigir envio de notificações por email quando o SMS estiver desabilitado
Atualiza modelo do banco de dados: **sim**
### 1.2.0 (2020-04-29)
📦 Downloads: [ZIP](https://cdn.lacunasoftware.com/signer/signer-1.2.0.zip) · [tar.gz](https://cdn.lacunasoftware.com/signer/signer-1.2.0.tar.gz)
* Novas funcionalidades
* [SIG-284] - Permitir subir um arquivo para ser validado na tela de validação do documento
* [SIG-296] - Configurar margens da marcação lateral na versão para impressão
* Melhorias
* [SIG-225] - Melhorar desempenho da Assinatura em Lote
* Correções de bugs
* [SIG-292] - Uniformizar exibição do nome e apelido de subscriptions entre os componentes da SPA e Signer
* [SIG-295] - Corrigir assinatura XAdES sem parâmetros
* [SIG-298] - Erro ao tentar obter o fuso de Brasília no Linux
Atualiza modelo do banco de dados: **sim**
# [Referência rápida do Amplia](https://docs.lacunasoftware.com/articles/amplia/quick-reference)
O essencial da API do Amplia em uma página, com links para os artigos completos e os samples.
## Para que serve
O Amplia é uma plataforma para criar e gerenciar Autoridades Certificadoras (ACs) ou Infraestruturas de Chaves Públicas (ICPs) completas, usada como serviço em nuvem da Lacuna (SaaS) ou como instância on-premises própria. Os operadores gerenciam ACs, modelos e pedidos em um dashboard web, e os seus sistemas criam pedidos de certificado e emitem certificados pela API REST.
| Alternativa | Quando é a melhor escolha |
|---|---|
| [Amplia Reg](https://docs.lacunasoftware.com/articles/amplia-reg) | Agentes de registro precisam validar e aprovar a identidade do titular antes da emissão. O Amplia Reg é a Autoridade de Registro (AR): a sua aplicação cria o pedido pela API do próprio Amplia Reg, e o Amplia só emite o certificado no final. |
| [Lacuna PSC](https://docs.lacunasoftware.com/articles/psc) | Você precisa guardar e gerenciar certificados em nuvem por trás de uma interface DOC-ICP-17.01 (OAuth 2.0). O PSC é um sistema complementar que depende do Amplia. |
| [GrantID](https://docs.lacunasoftware.com/articles/grant-id) | Você precisa de login, single sign-on ou múltiplos fatores de autenticação para as suas aplicações, e não de certificados. O próprio Amplia usa o GrantID para o login dos usuários. |
| [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk/quick-reference) | Você só precisa decodificar e validar certificados (ou decodificar, validar e emitir certificados de atributo) dentro de uma aplicação .NET, sem operar uma AC. |
| [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki/quick-reference) ou [Signer](https://docs.lacunasoftware.com/articles/signer/quick-reference) | Você precisa assinar documentos. Os certificados emitidos pelo Amplia podem ser usados logo em seguida para assinar com eles. |
## Instalação
A API REST não exige pacote: qualquer cliente HTTP pode chamá-la, e a descrição OpenAPI dela (versão 5.0.1 da API) está em https://docs.lacunasoftware.com/openapi/amplia.json. Bibliotecas cliente:
| Linguagem | Pacote | Major atual | Registro |
|---|---|---|---|
| .NET | `Lacuna.Amplia.Client` | 5 | [NuGet](https://www.nuget.org/packages/Lacuna.Amplia.Client) |
| Java | `com.lacunasoftware.amplia:amplia-client` | 1 | [Maven Central](https://central.sonatype.com/artifact/com.lacunasoftware.amplia/amplia-client) |
| Node.js | `amplia-client` | 1 | [npm](https://www.npmjs.com/package/amplia-client) |
| PHP | `lacuna/amplia-client` | 1 | [Packagist](https://packagist.org/packages/lacuna/amplia-client) |
| Python | `amplia-client` | 1 | [PyPI](https://pypi.org/project/amplia-client/) |
* O cliente .NET é gerado a partir do mesmo código-fonte do Amplia: obtenha um `IAmpliaService` por injeção de dependência (`services.AddAmplia(...)`)
ou de `AmpliaServicesFactory.GetService(endpoint, apiKey)`; a classe mais antiga `AmpliaClient` está marcada como `[Obsolete]`. Ao contrário dos
outros clientes, ele também envia o link de emissão por e-mail ou SMS e lê o QR code dele. Veja [Cliente .NET](https://docs.lacunasoftware.com/articles/amplia/integration#cliente-net).
* As majors dos clientes não acompanham a versão do Amplia: todo endpoint que os clientes 1.x chamam é atendido pelo Amplia 5.0.1.
## Configuração e autenticação
* **Chave de API**: toda requisição envia a chave de API da sua aplicação no header `X-Api-Key`, no formato `yourapp|xxxx…`.
Para obter uma, entre no dashboard do Amplia, crie uma aplicação em **Aplicações**, marque o papel dela, depois adicione uma chave em **Chaves** e copie-a, pois ela não pode ser recuperada depois:
veja [Criar uma aplicação e uma chave de API](https://docs.lacunasoftware.com/articles/amplia/integration#criar-uma-aplicação-e-uma-chave-de-api).
Uma aplicação que executa o fluxo inteiro precisa do papel **Operador** (*Worker*). Uma aplicação com o papel **Agente de Registro** pode criar pedidos, pedir ao Amplia que envie o link de emissão por e-mail ou SMS e revogar, mas não pode ler o link de emissão nem emitir certificados.
* **Endpoint**: o endereço da instância do Amplia, prefixado a toda URL relativa da API.
A instância em nuvem da Lacuna é `https://amplia.lacunasoftware.com/`, o endpoint padrão nos samples; com uma instância on-premises, use o [domínio do dashboard](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain) que você escolheu.
TODO(docs): nenhum endpoint separado de sandbox ou de teste está documentado.
* **Idioma**: a API observa o header `Accept-Language` (`en-US` ou `pt-BR`); as mensagens de erro não são afetadas por ele.
* **Webhook**: o Amplia pode avisar o seu sistema sempre que um certificado for emitido; o corpo da notificação é um `WebhookEventModel` cujo `type` é `OrderCertificateIssued`.
Um **Administrador** ou **Gerente** define a URL e o tipo de autenticação (o header que o Amplia envia: `Bearer`, `ApiKey`, um `Authorization` próprio ou `Basic`) em **Configurações** > **Configuração de webhook**.
O Amplia envia JSON com nomes de propriedade em PascalCase (`Type`, `Order`, `Certificate`) e considera entregue qualquer resposta 2xx; o valor `Basic` dele não segue a codificação padrão. Veja [Webhook](https://docs.lacunasoftware.com/articles/amplia/integration#webhook).
Os samples em Node.js guardam `caId` (a AC que emite os certificados), `apiKey` e `endpoint` (com `/` no final) em `config/default.js`, e criam o cliente a partir do endpoint e da chave de API:
```js
const { AmpliaClient } = require("amplia-client");
class Util {
static getAmpliaClient() {
let CONFIG = Config.getInstance().get('amplia');
let key = CONFIG['apiKey'];
let endpoint = CONFIG['endpoint'];
return new AmpliaClient({
endpoint: endpoint,
apiKey: key
});
}
}
```
## Operações principais
Toda emissão começa com um pedido em uma AC; depois, ou o titular emite o certificado no próprio dispositivo, ou o seu servidor o emite. O código é Node.js, recortado dos [samples do PKI Suite](https://github.com/LacunaSoftware/PkiSuiteSamples); os blocos seguintes usam o `order` criado no primeiro.
O [guia de integração](https://docs.lacunasoftware.com/articles/amplia/integration) cobre cada fluxo em Java, Node.js, PHP e Python, com as chamadas REST por trás deles.
### Criar um pedido de certificado
Um pedido guarda os dados do titular, a AC que emite o certificado e a validade dele. `client.createOrder(request)` envia para `POST /api/orders/{format}`, por exemplo `/api/orders/pki-brazil` para parâmetros ICP-Brasil. Guia: [Criar pedidos de certificado](https://docs.lacunasoftware.com/articles/amplia/integration/orders). Sample completo: [issue-cert-server-amplia.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/issue-cert-server-amplia.js).
```js
const {PkiBrazilCertificateParameters, CreateOrderRequest, CertificateKinds} = require('amplia-client');
const CONFIG = Config.getInstance().get('amplia');
const client = Util.getAmpliaClient();
const request = new CreateOrderRequest({
caId: CONFIG['caId'],
validityEnd: Util.getTwoYearsFromNowDate(),
kind: CertificateKinds.PUBLIC_KEY,
parameters: new PkiBrazilCertificateParameters({
name: req.body.subjectName,
cpf: req.body.cpf
})
});
let order = await client.createOrder(request);
```
### Deixar o titular emitir na própria máquina
`client.getOrderIssueLink(orderId, returnUrl)` (`GET /api/orders/{id}/issue-link?returnUrl=…`) devolve um link para a página de emissão do Amplia, em que o par de chaves é gerado na máquina do usuário; depois, o Amplia volta para `returnUrl`. O sample também define `phoneNumber` nos parâmetros do pedido, de que esse fluxo precisa para verificar a identidade do titular. Guia: [Emitir na máquina do titular](https://docs.lacunasoftware.com/articles/amplia/integration/issue-on-user-machine). Sample completo: [issue-cert-user-amplia.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/issue-cert-user-amplia.js).
```js
const RETURN_URL = 'http://localhost:3000/issue-cert-user-amplia/complete';
let link = await client.getOrderIssueLink(order.getId(), RETURN_URL);
res.render('issue-cert-user-amplia/redirect', {
redirectLink: link
});
```
O Amplia também pode entregar o link ele mesmo. `POST /api/orders/{id}/send-issue-email-request` o envia por e-mail ao endereço que está nos parâmetros do pedido, e `POST /api/orders/{id}/send-issue-sms-request` o envia por SMS ao telefone do pedido; como o seu sistema nunca vê o link, uma aplicação com o papel **Agente de Registro** pode usá-los.
`GET /api/orders/{id}/issue-qrcode-image` devolve um QR code em PNG que leva à página de emissão, e `issue-qrcode-data`, a URL que ele contém. Detalhes: [Outras formas de entregar o link de emissão](https://docs.lacunasoftware.com/articles/amplia/integration/issue-on-user-machine#outras-formas-de-entregar-o-link-de-emissão).
### Emitir a partir de um CSR gerado no seu servidor
`client.issueCertificate(orderId, csr, keyMedia)` (`POST /api/v2/certificates`, corpo `orderId`, `csr`, `keyMedia`) emite o pedido para um CSR que você gerou; o sample gera a chave e o CSR com o PKI Express. `keyMedia` é `PC`, `CryptoDevice`, `Mobile` ou `Cloud`.
Guia: [Emitir a partir de um CSR](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr). Sample completo: [issue-cert-server-amplia.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/issue-cert-server-amplia.js).
```js
const {KeyGenerator} = require('pki-express');
const {KeyMedia } = require('amplia-client/lib/enums');
const keyGenerator = new KeyGenerator({
keySize: req.body.keySize,
genCsr: true
});
let genKey = await keyGenerator.generate();
let generatedKey = genKey.getKey();
let cert = await client.issueCertificate(order.getId(), genKey.getCsr(), KeyMedia.PC);
let certId = cert.getId();
// WARNING: It is highly RECOMMENDED to ENCRYPT the key before storing
// in your database.
StorageMock.storeSync(generatedKey, certId, '.json');
StorageMock.storeSync(cert.getContentRaw(), certId, '.cer');
```
### Emitir um arquivo PKCS #12 (.pfx)
`client.issuePkcs12Certificate(orderId, password, keySize)` (`POST /api/certificates/pkcs12`) faz o Amplia gerar a chave e devolver um arquivo PKCS #12 protegido por senha. Guia: [Emitir um arquivo PKCS #12](https://docs.lacunasoftware.com/articles/amplia/integration/pkcs12). Sample completo: [issue-pkcs12-cert-server-amplia.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/issue-pkcs12-cert-server-amplia.js).
```js
let cert = await client.issuePkcs12Certificate(order.getId(), req.body.senha, 2048);
let certId = cert.getModel().id;
StorageMock.storeSync(cert.getPfxContent(), certId, '.pfx');
```
### Emitir um certificado de atributo
Crie o pedido com `kind` igual a `Attribute` e parâmetros CIE (a Carteira de Identificação Estudantil), em uma AC configurada para certificados de atributo, que não é a AC que emite certificados de chave pública; depois, emita-o sem CSR, já que um certificado de atributo não tem par de chaves próprio. O endpoint de pedidos CIE é `POST /api/orders/cie`. Guia: [Certificados de atributo](https://docs.lacunasoftware.com/articles/amplia/integration/attribute-certificates). O único sample é em PHP: [complete.php](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/issue-cert-attribute-amplia/complete.php).
### Revogar um certificado
O método `client.revokeCertificate(certificateId)` do cliente Node.js envia `DELETE /api/certificates/{id}`. O Amplia publica LCRs (CRLs) nos seus [domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains) e pode responder a requisições [OCSP](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-ocsp).
A chamada não recebe motivo de revogação e não pode ser desfeita. Cada AC emite LCRs no seu próprio cronograma (a cada `crlIssuePeriod`), então uma revogação aparece na próxima LCR programada, sem código de motivo, enquanto o respondedor OCSP do Amplia a informa na hora. Guia: [Revogar certificados](https://docs.lacunasoftware.com/articles/amplia/integration/revocation).
**Renovação**: o Amplia pode renovar certificados, e o titular recebe um novo certificado no lugar do que está vencendo.
TODO(docs): nenhum artigo documenta como renovar um certificado pela API.
## Erros
A API responde com um código de status HTTP; nos erros de API, o corpo é um `ErrorModel` com `code`, `message` e `details`. As mensagens de erro não seguem o `Accept-Language`: registre-as no log em vez de mostrá-las aos usuários.
| Status | Significado |
|---|---|
| 400 Bad Request | Erro de sintaxe, por exemplo, um campo obrigatório ausente |
| 401 Unauthorized | Chave de API não informada ou inválida |
| 403 Forbidden | A chave de API é válida, mas a aplicação não tem permissão para a operação |
| 422 Unprocessable Entity | Erro de API; o corpo é um `ErrorModel` |
| 500 Internal Server Error | Erro inesperado; o `exceptionCode` no corpo ajuda o suporte da Lacuna |
O cliente Node.js rejeita com `AmpliaError` em um 422 (a mensagem traz o `code`), com `RestError` nos demais erros HTTP e com `RestUnreachableError` quando o endpoint não pode ser acessado. O enum `AmpliaErrorCodes` do cliente cita códigos como `OrderNotFound`, `OrderAlreadyFulfilled` e `CertificateAlreadyRevoked`.
O cliente .NET lança `AmpliaException`, cujo `Code` é o código de erro.
[Códigos de erro](https://docs.lacunasoftware.com/articles/amplia/integration/errors#códigos-de-erro) lista os códigos que as chamadas de emissão devolvem e o que cada um significa. Uma permissão que falta à aplicação recebe 403 Forbidden, e não um código de erro.
## Certificados e credenciais de teste
* [Certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs): arquivos PKCS #12 da ICP de testes da Lacuna, com senha `1234`, só para desenvolvimento e homologação.
* O `config/default.js` do sample em Node.js vem com uma chave de API de avaliação (trial) para a instância em nuvem da Lacuna, que expira em 31/10/2026, e com o id de uma AC de exemplo;
os modelos de configuração dos samples em Node.js e PHP pedem, em vez disso, a sua própria chave e o id da sua AC.
* Quando os samples deixam de funcionar, por exemplo depois que a chave deles expira, a configuração dos samples pede que você contate o suporte da Lacuna em suporte@lacunasoftware.com.
## Experimente
Emita certificados de teste no site de demonstração ou rode os samples em Java, Node.js, PHP e Python:
**Demonstrações e exemplos**
- Demonstração: [Issue an e-CNPJ certificate](https://try.lacunasoftware.com/pt/amplia/issue-e-cnpj-certificate)
- Demonstração: [Issue an e-CPF certificate](https://try.lacunasoftware.com/pt/amplia/issue-e-cpf-certificate)
- Demonstração: [Issue an e-CPF with your face](https://try.lacunasoftware.com/pt/amplia/issue-an-ecpf-with-your-face)
- Demonstração: [Issue an SSL/TLS certificate](https://try.lacunasoftware.com/pt/amplia/issue-ssl-tls-certificate)
- Issue a certificate with the key on the user's machine using Amplia: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/IssueCertUserAmpliaController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/issue-cert-user-amplia.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/issue-cert-user-amplia/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/issue_cert_user_amplia.py)
- Issue a certificate with the key on the server using Amplia: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/IssueCertServerAmpliaController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/issue-cert-server-amplia.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/issue-cert-server-amplia/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/issue_cert_server_amplia.py)
- Issue a PKCS #12 certificate using Amplia: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/issue-pkcs12-cert-server-amplia.js)
- Issue an attribute certificate using Amplia: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/issue-cert-attribute-amplia/index.php)
# [Amplia](https://docs.lacunasoftware.com/articles/amplia)
O Amplia é uma plataforma para criar e gerenciar Autoridades Certificadoras (ACs) ou Infraestruturas de Chaves Públicas (ICPs) completas.

## Funcionalidades
### Tipos de certificado
* **Certificados de chave pública**: certificados X.509 para pessoas físicas e jurídicas. A chave do titular pode ser gerada em software
(A1), em token criptográfico ou smart card (A3), ou ficar guardada no próprio Amplia, protegida por PIN, como *certificado em nuvem*
(veja [gerenciamento de certificados em nuvem](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-cert-management)).
* **Certificados SSL/TLS**: certificados de servidor que seguem as diretrizes do CA/Browser Forum e aceitam nomes wildcard e endereços
IP, além dos nomes DNS comuns.
* **Certificados de atributo**: em vez de vincular uma chave ao titular, atestam uma característica dele, como uma carteira profissional
(*PrivateID*) ou a Carteira de Identificação Estudantil (CIE).
* **Certificados de AC**: monte a própria hierarquia, com ACs raiz, intermediárias e de dispositivos, ou subordine suas ACs a uma AC-pai
operada por terceiros. Veja o [Guia de Operação](https://docs.lacunasoftware.com/articles/amplia/operation).
* **Perfis personalizados**: os modelos (templates) definem os campos, as políticas de certificado e as extensões de cada tipo de
certificado, para que os certificados emitidos atendam às suas necessidades de identificação.
### Emissão e operação
* **Pedidos e emissão pelo titular**: um operador ou o seu sistema cria um pedido de certificado, e o titular recebe um link de emissão
por e-mail ou SMS, ou lê um QR code, para emitir o certificado por conta própria. Um código enviado por SMS confirma a identidade do
titular.
* **Desktop e mobile**: o titular emite no Windows, no Linux ou no macOS, com a chave em software ou em um dispositivo criptográfico, ou
em celulares Android e iOS.
* **Renovação**: os certificados podem ser renovados, e o titular recebe um novo certificado no lugar do que está vencendo.
* **Revogação**: o Amplia publica LCRs (CRLs) nos [domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains) que você escolher, pode
responder a requisições [OCSP](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-ocsp) e pode [copiar LCRs e certificados de AC](https://docs.lacunasoftware.com/articles/amplia/on-premises/crl-propagation)
para um armazenamento externo.
* **Dashboard e API REST**: os operadores gerenciam ACs, modelos e pedidos em um dashboard web, e os seus sistemas podem criar pedidos e
emitir certificados pela API REST.
### Perfis por país
O Amplia já vem com perfis de certificado para as ICPs nacionais do Brasil (ICP-Brasil), do Paraguai, do Equador e de Cabo Verde. Para
qualquer outro país, ou para uma ICP privada, você mesmo define os perfis.
## Integrações
* **Repositórios de chaves e HSMs**: as chaves das ACs, e também as dos certificados em nuvem, podem ficar no Azure Key Vault, em HSMs Dinamo ou
Kryptus, em qualquer dispositivo PKCS #11, em providers CAPI/CNG do Windows ou, criptografadas, no banco de dados. Onde guardá-las é
uma das principais decisões no planejamento de uma AC. Veja [Repositórios de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores).
* **Login**: os usuários entram pelo [GrantID](https://docs.lacunasoftware.com/articles/grant-id), o provedor de identidade OpenID Connect da Lacuna, que pode exigir
certificado digital no login. Veja [Configuração do OpenID Connect](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-oidc).
* **Notificações**: os links de emissão e os códigos de confirmação chegam ao titular por [e-mail](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-email) e por
[SMS](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-sms), por meio de provedores como Twilio, Zenvia, Amazon SNS e TotalVoice. Um webhook pode avisar o seu
sistema sempre que um certificado for emitido.
* **Armazenamento de arquivos**: os termos assinados e outros arquivos ficam no sistema de arquivos local, no Azure Blob Storage ou no
Amazon S3 (ou em um serviço compatível com S3). Veja [Configuração de blob storage](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-blob-storage).
* **Produtos Lacuna**: o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) permite que o titular gere a chave no próprio computador na emissão pelo
desktop, o [Lacuna PSC](https://docs.lacunasoftware.com/articles/psc) usa o Amplia para guardar certificados em nuvem e o [Amplia Reg](https://docs.lacunasoftware.com/articles/amplia-reg)
acrescenta uma Autoridade de Registro (AR). Os certificados emitidos pelo Amplia podem ser usados logo em seguida para assinar com o
[Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) e o [Signer](https://docs.lacunasoftware.com/articles/signer).
## Implantação
Escolha onde o Amplia roda:
* **Nuvem (SaaS)**: use a instância hospedada pela Lacuna, sem nada para instalar ou manter. As chaves das ACs ficam em HSMs do Azure
Key Vault.
* **On-premises**: rode a sua própria instância no [Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows), no [Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux),
no [Docker](https://docs.lacunasoftware.com/articles/amplia/on-premises/docker) ou no [Azure App Service](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure), com as chaves nos HSMs que você
escolher. Comece pelo artigo [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises), que trata das decisões a tomar antes da instalação.
## Origem do nome
AMPLIA é um [acrônimo recursivo](https://pt.wikipedia.org/wiki/Acr%C3%B4nimo_recursivo) em inglês para "AMPLIA is a Modern Private Label Issuing Authority".
## Veja também
* [Guia de integração](https://docs.lacunasoftware.com/articles/amplia/integration), para criar pedidos e emitir certificados pela API
* [Guia de Operação](https://docs.lacunasoftware.com/articles/amplia/operation)
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
* [Histórico de versões](https://docs.lacunasoftware.com/articles/amplia/changelog)
# [Guia de integração - Amplia](https://docs.lacunasoftware.com/articles/amplia/integration)
O seu sistema pode criar pedidos de certificado e emitir certificados pela API REST do [Amplia](https://docs.lacunasoftware.com/articles/amplia), enquanto os operadores
gerenciam ACs, modelos e pedidos no dashboard. Este guia cobre as chamadas
da API; para um resumo em uma página, veja a [referência rápida do Amplia](https://docs.lacunasoftware.com/articles/amplia/quick-reference).
## Antes de começar
Você precisa de três valores:
* **Chave de API**: toda requisição envia a chave de API da sua aplicação no header `X-Api-Key`, no formato `yourapp|xxxx…`.
Para obter uma, [crie uma aplicação e uma chave de API](https://docs.lacunasoftware.com/articles/amplia/integration#criar-uma-aplicação-e-uma-chave-de-api) no dashboard do Amplia.
* **Endpoint**: o endereço da instância do Amplia, prefixado a toda URL relativa da API.
A instância em nuvem da Lacuna é `https://amplia.lacunasoftware.com/`, o padrão nos samples; com uma instância on-premises, use o
[domínio do dashboard](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain) que você escolheu.
TODO(docs): nenhum endpoint separado de sandbox ou de teste está documentado.
* **Id da AC**: o id da AC que emite os certificados, enviado como `caId` ao criar um pedido. Os samples o guardam na configuração, ao
lado da chave de API; o sample de certificado de atributo usa uma segunda AC, já que certificados de atributo precisam de uma AC
configurada para eles.
Os comentários dos samples acrescentam que uma aplicação pode ter uma AC padrão e, nesse caso, `caId` pode ser omitido.
TODO(docs): onde se define a AC padrão de uma aplicação.
A API observa o header `Accept-Language` (`en-US` ou `pt-BR`), que também define o idioma dos links de emissão que ela devolve; as
mensagens de erro não são afetadas por ele.
### Criar uma aplicação e uma chave de API
O seu sistema chama a API como uma **aplicação** de uma conta do Amplia, com um papel que define o que ela pode fazer:
1. Entre no dashboard do Amplia e clique em **Aplicações** no menu lateral, depois em *Adicionar*.
1. Preencha um nome, selecione a conta na qual os certificados são emitidos e marque o papel da aplicação (veja abaixo), depois clique
em *Criar*.
1. Clique em **Chaves**, depois em *Adicionar*. Preencha uma descrição e uma expiração, depois clique em *Criar*.
1. Copie a chave de API: ela não pode ser recuperada depois.
| Papel | Criar pedidos, pedir ao Amplia que envie o link de emissão por e-mail ou SMS, revogar | Obter o link de emissão ou o QR code, emitir certificados | Configuração de webhook |
|---|---|---|---|
| **Operador** (*Worker*) | Sim | Sim | Não |
| **Agente de Registro** (*Registration Agent*) | Sim | Não | Não |
| **Gerente** (*Manager*), **Administrador** (*Admin*) | Sim | Sim | Sim |
Um sistema que executa o fluxo inteiro precisa do papel **Operador**, o mesmo que o [Amplia Reg](https://docs.lacunasoftware.com/articles/amplia-reg) usa. Uma
aplicação com o papel **Agente de Registro** pode criar pedidos e enviar o link de emissão ao titular por e-mail ou SMS, mas não pode ler
o link nem emitir o certificado ela mesma.
## Bibliotecas cliente
Qualquer cliente HTTP pode chamar a API, cuja descrição OpenAPI está em https://docs.lacunasoftware.com/openapi/amplia.json. A Lacuna
publica bibliotecas cliente, que os samples usam:
| Linguagem | Pacote | Registro |
|---|---|---|
| .NET | `Lacuna.Amplia.Client` | [NuGet](https://www.nuget.org/packages/Lacuna.Amplia.Client) |
| Java | `com.lacunasoftware.amplia:amplia-client` | [Maven Central](https://central.sonatype.com/artifact/com.lacunasoftware.amplia/amplia-client) |
| Node.js | `amplia-client` | [npm](https://www.npmjs.com/package/amplia-client) |
| PHP | `lacuna/amplia-client` | [Packagist](https://packagist.org/packages/lacuna/amplia-client) |
| Python | `amplia-client` | [PyPI](https://pypi.org/project/amplia-client/) |
As majors dos clientes não acompanham a versão do Amplia: todo endpoint que os clientes 1.x abaixo chamam é atendido pelo Amplia 5.0.1.
O cliente .NET é descrito [mais abaixo](https://docs.lacunasoftware.com/articles/amplia/integration#cliente-net).
Os clientes Java (1.2.1), Node.js (1.3.3), PHP (1.3.0) e Python (1.0.0) encapsulam estas chamadas:
| Operação | Node.js, Java, PHP | Python | Endpoint |
|---|---|---|---|
| Criar um pedido | `createOrder` | `create_order` | `POST /api/orders/{format}` |
| Ler um pedido | `getOrder` | `get_order` | `GET /api/v2/orders/{id}` (Java: `GET /api/orders/{format}/{id}`) |
| Obter o link de emissão | `getOrderIssueLink` | `get_order_issue_link` | `GET /api/orders/{id}/issue-link` |
| Emitir a partir de um CSR | `issueCertificate` | `issue_certificate` | `POST /api/v2/certificates` |
| Emitir um arquivo PKCS #12 | `issuePkcs12Certificate` (só Node.js) | | `POST /api/certificates/pkcs12` |
| Revogar um certificado | `revokeCertificate` | `revoke_certificate` | `DELETE /api/certificates/{id}` |
Nenhum dos quatro envia o link de emissão por e-mail ou SMS nem lê o QR code dele: chame esses endpoints diretamente, ou use o cliente .NET (veja
[Outras formas de entregar o link de emissão](https://docs.lacunasoftware.com/articles/amplia/integration/issue-on-user-machine#outras-formas-de-entregar-o-link-de-emissão)).
Cada sample cria o cliente a partir do endpoint e da chave de API:
**Node.js**
```js
const { AmpliaClient } = require("amplia-client");
static getAmpliaClient() {
let CONFIG = Config.getInstance().get('amplia');
let key = CONFIG['apiKey'];
let endpoint = CONFIG['endpoint'];
return new AmpliaClient({
endpoint: endpoint,
apiKey: key
});
}
```
**Java**
```java
import com.lacunasoftware.amplia.AmpliaClient;
public static AmpliaClient getAmpliaClient() {
String apiKey = getProperties().getAmplia().getApiKey();
String endpoint = getProperties().getAmplia().getEndpoint();
if (endpoint == null || endpoint.length() == 0) {
endpoint = "https://amplia.lacunasoftware.com/";
}
return new AmpliaClient(endpoint, apiKey);
}
```
**PHP**
```php
use Lacuna\Amplia\AmpliaClient;
public static function getAmpliaClient()
{
$config = getConfig();
$ampliaApiKey = $config['amplia']['apiKey'];
$ampliaEndpoint = $config['amplia']['endpoint'];
return new AmpliaClient($ampliaEndpoint, $ampliaApiKey);
}
```
**Python**
```python
from amplia_client import AmpliaClient
def get_amplia_client():
api_key = current_app.config['AMPLIA_API_KEY']
endpoint = current_app.config['AMPLIA_ENDPOINT']
return AmpliaClient(endpoint, api_key)
```
### Cliente .NET
O `Lacuna.Amplia.Client` 5.0.0 tem como alvo o .NET Standard 2.0 e é gerado a partir do mesmo código-fonte do Amplia 5.0.1. Obtenha um
`IAmpliaService` por injeção de dependência, depois de `services.AddAmplia(...)`, ou de `AmpliaServicesFactory.GetService(endpoint, apiKey)`;
a classe `AmpliaClient` ainda funciona, mas está marcada como `[Obsolete]`.
```csharp
using Lacuna.Amplia.Api;
using Lacuna.Amplia.Api.CertificateFormatting;
using Lacuna.Amplia.Api.Orders;
using Lacuna.Amplia.Client;
IAmpliaService amplia = AmpliaServicesFactory.GetService("https://amplia.lacunasoftware.com/", apiKey);
var order = await amplia.CreateOrderAsync(new CreateOrderRequest2 {
CAId = caId,
Kind = CertificateKinds.PublicKey,
ValidityEnd = DateTimeOffset.Now.AddYears(2),
Parameters = new PkiBrazilCertificateParameters {
Name = subjectName,
Cpf = cpf,
PhoneNumber = phoneNumber,
},
});
string issueLink = await amplia.GetOrderIssueLinkAsync(order.Id, returnUrl);
```
No ASP.NET Core, `services.AddAmplia(o => { o.Endpoint = …; o.ApiKey = …; })`, ou `services.AddAmplia().Configure(configuration.GetSection("Amplia"))`,
registra o `IAmpliaService`; `AmpliaOptions` também tem `Timeout`, `Culture` (enviado como `Accept-Language`) e `DefaultSubscriptionId`.
| Operação | Método .NET | Endpoint |
|---|---|---|
| Criar um pedido | `CreateOrderAsync` | `POST /api/orders/{format}` (`POST /api/v2/orders` para a requisição não genérica) |
| Ler um pedido | `GetOrderAsync` | `GET /api/v2/orders/{id}` |
| Obter o link de emissão | `GetOrderIssueLinkAsync` | `GET /api/orders/{id}/issue-link` |
| Enviar o link de emissão por e-mail ou SMS | `SendIssueEmailAsync`, `SendIssueSmsAsync` | `POST /api/orders/{id}/send-issue-email-request`, `POST /api/v2/orders/{id}/send-issue-sms-request` |
| Obter o QR code | `GetOrderIssueQRCodeDataAsync`, `GetOrderIssueQRCodeImageAsync` | `GET /api/orders/{id}/issue-qrcode-data`, `issue-qrcode-image` |
| Emitir a partir de um CSR | `IssueCertificateAsync` | `POST /api/v2/certificates` |
| Emitir um arquivo PKCS #12 | `IssuePkcs12CertificateAsync` | `POST /api/certificates/pkcs12` |
| Ler um certificado | `GetCertificateAsync` | `GET /api/v2/certificates/{id}` |
| Revogar um certificado | `RevokeCertificateAsync` | `DELETE /api/certificates/{id}` |
Erros de API (422) lançam `AmpliaException`, cujo `Code` é o código de erro, ou `OrderLockedException` quando o código é `OrderLocked`.
## Como a emissão funciona
Todo certificado começa com um **pedido** em uma AC, que guarda os dados do titular e a validade do certificado. Depois, o pedido é
atendido, emitindo um certificado, de uma destas formas:
| Onde o par de chaves é gerado | Como emitir | Artigo |
|---|---|---|
| No computador do titular, pela página de emissão do Amplia | Envie ao titular um link de emissão | [Emitir na máquina do titular](https://docs.lacunasoftware.com/articles/amplia/integration/issue-on-user-machine) |
| No seu servidor, ou no navegador do usuário com o Web PKI | Envie ao Amplia um CSR | [Emitir a partir de um CSR](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr) |
| Pelo Amplia, que o devolve em um arquivo protegido por senha | Peça um PKCS #12 (.pfx) | [Emitir um arquivo PKCS #12](https://docs.lacunasoftware.com/articles/amplia/integration/pkcs12) |
| Sem par de chaves: o certificado atesta um atributo do titular | Emita sem CSR | [Certificados de atributo](https://docs.lacunasoftware.com/articles/amplia/integration/attribute-certificates) |
Comece por [Criar pedidos de certificado](https://docs.lacunasoftware.com/articles/amplia/integration/orders). Depois:
* [Emitir na máquina do titular](https://docs.lacunasoftware.com/articles/amplia/integration/issue-on-user-machine)
* [Emitir a partir de um CSR](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr)
* [Emitir um arquivo PKCS #12](https://docs.lacunasoftware.com/articles/amplia/integration/pkcs12)
* [Certificados de atributo](https://docs.lacunasoftware.com/articles/amplia/integration/attribute-certificates)
* [Revogar certificados](https://docs.lacunasoftware.com/articles/amplia/integration/revocation)
* [Erros](https://docs.lacunasoftware.com/articles/amplia/integration/errors)
## Webhook
O Amplia pode avisar o seu sistema sempre que um certificado for emitido: o corpo da notificação é um `WebhookEventModel` cujo `type` é
`OrderCertificateIssued`, com o `order` e, depois da emissão, o `certificate`.
O webhook pertence à conta. Um usuário com o papel **Administrador** ou **Gerente** o define no dashboard, em **Configurações** >
**Configuração de webhook**:
* **Habilitar webhook**, a **URL** para a qual o Amplia envia a notificação e o **Tipo de autenticação**, que define o header que o
Amplia envia com cada notificação:
| Tipo de autenticação | Header |
|---|---|
| `NoAuth` | Nenhum header de autenticação |
| `Bearer` | `Authorization: Bearer ` |
| `ApiKey` | `X-Api-Key: ` |
| `CustomAuthorizationHeader` | `Authorization: ` |
| `Basic` | `Authorization: Basic ` (veja o aviso abaixo) |
* A aba **Chamadas recentes** lista as notificações enviadas, com o código de status e, nas chamadas que falharam, a resposta.
O Amplia envia um `POST` com um corpo JSON e considera entregue qualquer resposta 2xx. O JSON mantém os nomes de propriedade dos modelos
do Amplia, em PascalCase (`Type`, `Order`, `Certificate`), com os enums por nome:
```json
{
"Type": "OrderCertificateIssued",
"Order": { "Id": "…", "Status": "Issued", "CertificateId": "…" },
"Certificate": { "Id": "…" }
}
```
> **Cuidado: Autenticação Basic**
>
> Com `Basic`, o dashboard codifica o nome de usuário e a senha separadamente e os une com dois-pontos, `base64(usuário):base64(senha)`, em
> vez do padrão `base64(usuário:senha)`. Um tratador de autenticação Basic padrão rejeita esse valor, então prefira `Bearer`, `ApiKey` ou
> `CustomAuthorizationHeader`.
TODO(docs): se e com que frequência o Amplia tenta de novo uma notificação que falhou.
## Veja também
* [Referência rápida do Amplia](https://docs.lacunasoftware.com/articles/amplia/quick-reference)
* [Guia de operação](https://docs.lacunasoftware.com/articles/amplia/operation)
* [Changelog](https://docs.lacunasoftware.com/articles/amplia/changelog)
# [Criar pedidos de certificado](https://docs.lacunasoftware.com/articles/amplia/integration/orders)
Todo certificado que o [Amplia](https://docs.lacunasoftware.com/articles/amplia) emite começa com um pedido: ele indica a AC que emite o certificado e guarda a validade do
certificado e os dados do titular.
Um pedido gera um certificado: depois da emissão, o `status` do pedido é `Issued` e o `certificateId` dele aponta para o certificado.
Veja [Antes de começar](https://docs.lacunasoftware.com/articles/amplia/integration#antes-de-começar) para a chave de API, o endpoint e o id da AC.
## Endpoints por formato de certificado
Cada formato de certificado tem o próprio endpoint, `POST /api/orders/{format}`, cujo corpo recebe os campos comuns abaixo mais um objeto
`parameters` daquele formato:
| Formato | Endpoint | `parameters` |
|---|---|---|
| ICP-Brasil (e-CPF, e-CNPJ) | `POST /api/orders/pki-brazil` | `PkiBrazilCertificateParameters` |
| SSL/TLS | `POST /api/orders/ssl` | `SslCertificateParameters` |
| Carteira de Identificação Estudantil (CIE), um certificado de atributo | `POST /api/orders/cie` | `CieCertificateParameters` |
| PrivateID, um certificado de atributo | `POST /api/orders/private-id` | `PrivateIDCertificateParameters` |
| Paraguai | `POST /api/orders/pki-paraguay` | `PkiParaguayCertificateParameters` |
| Equador | `POST /api/orders/bce` | `EcuadorBceCertificateParameters` |
| Cabo Verde | `POST /api/orders/pki-cabo-verde` | `PkiCaboVerdeCertificateParameters` |
| Perfil personalizado | `POST /api/orders/custom` | `CustomCertificateParameters` |
A API também tem `POST /api/orders/latinus` e `POST /api/orders/br-laboral`, e um `POST /api/v2/orders` genérico, cujo `parameters` pode
ser de qualquer formato.
TODO(docs): para que servem os formatos Latinus e BRLaboral.
As bibliotecas cliente escolhem o endpoint pela classe do objeto `parameters`. As quatro criam pedidos ICP-Brasil, SSL/TLS e CIE; o
cliente Node.js também cria pedidos do Paraguai, e o cliente Java, pedidos Latinus. Elas também enviam pedidos CNB e ARISP (e os clientes
Java e PHP, pedidos CNB CA) para `api/orders/cnb`, `api/orders/arisp` e `api/orders/cnb-ca`, que a descrição OpenAPI não lista.
O Amplia 5.0.1 atende esses três endpoints, com `CnbCertificateParameters`, `CnbCACertificateParameters` e
`ArispCertificateParameters`.
TODO(docs): os parâmetros dos pedidos CNB, CNB CA e ARISP (veja [Colégio Notarial do Brasil - Atribuição de OIDs](https://docs.lacunasoftware.com/articles/asn1-cnb) e [ARISP - Estruturas ASN.1](https://docs.lacunasoftware.com/articles/asn1-arisp) para esses certificados).
## Campos comuns
| Campo | Significado |
|---|---|
| `caId` | Id da AC que emite o certificado. |
| `templateId` | Id do modelo (perfil de certificado) a aplicar. |
| `kind` | `PublicKey` ou `Attribute`. |
| `parameters` | Os dados do titular, no formato do endpoint. Obrigatório. |
| `validityEnd`, `validityStart` | Datas até e a partir das quais o certificado é válido. |
| `validity` | Validade como intervalo de tempo: as chamadas gravadas das demos enviam `"365.00:00:00"` para um ano e `"90.00:00:00"` para 90 dias. |
| `digestAlgorithm` | `SHA1`, `SHA256`, `SHA384` ou `SHA512`. |
| `serialNumber` | Um número de série predefinido para o certificado. |
| `policyOid`, `policyStatementUrl`, `policyUserNotices`, `policies` | Políticas de certificado a incluir. |
| `issuePendingApproval` | Booleano; as chamadas gravadas das demos enviam `false`. |
* Os samples em Node.js e Python enviam `validityEnd` como data no padrão `MM-DD-YYYY`, dois anos depois da data atual.
* Um pedido sem validade, em um modelo que não tem validade padrão, é recusado.
* As bibliotecas cliente só enviam `caId`, `templateId`, `kind`, `parameters`, `validityEnd` e um campo para copiar de um certificado;
para definir os demais campos, chame a API diretamente. O cliente Java formata `validityEnd` como `MM-dd-yyyy`.
* TODO(docs): o que fazem `issuePendingApproval` e `copyFromCertificate`, e como um certificado pendente de aprovação é aprovado (`POST /api/certificates/{id}/approval`).
## Certificados ICP-Brasil (e-CPF e e-CNPJ)
`PkiBrazilCertificateParameters` exige `name` e `cpf`. Os demais campos incluem `emailAddress`, `birthDate`, `phoneNumber`, `cnpj` e
`companyName` para um e-CNPJ, os números de RG e OAB, `nis`, `cei`, `organization`, `organizationalUnits` e `country`.
* `phoneNumber` é necessário quando o titular emite o certificado na página de emissão do Amplia, que envia a ele um código de confirmação
por SMS.
* O Amplia tira o subject do certificado desses parâmetros, nunca de um CSR: coloque a identidade aqui, com o CPF e o CNPJ sem máscara e
os nomes sem acentos.
* `normalizeCommonName` e `appendIdentifierToCommonName` normalizam o Common Name (CN=) e acrescentam a ele o CPF ou o CNPJ.
* Nas chamadas gravadas das demos, um pedido com CPF volta com `certificateType` `e-CPF`, e um que também tem `cnpj` e `companyName`
volta como `e-CNPJ`.
* Os clientes Node.js, PHP e Python enviam as unidades organizacionais como `organizationUnits`, enquanto o campo da API é
`organizationalUnits`; o cliente Java usa o nome da API.
Os samples criam um pedido de e-CPF só com o nome e o CPF:
**Node.js**
```js
const {PkiBrazilCertificateParameters, CreateOrderRequest, CertificateKinds} = require('amplia-client');
const CONFIG = Config.getInstance().get('amplia');
const client = Util.getAmpliaClient();
const request = new CreateOrderRequest({
caId: CONFIG['caId'],
validityEnd: Util.getTwoYearsFromNowDate(),
kind: CertificateKinds.PUBLIC_KEY,
parameters: new PkiBrazilCertificateParameters({
name: req.body.subjectName,
cpf: req.body.cpf
})
});
let order = await client.createOrder(request);
```
**Java**
```java
AmpliaClient client = Util.getAmpliaClient();
CreateOrderRequest createOrderRequest = new CreateOrderRequest<>();
createOrderRequest.setCAId(ApplicationProperties.getInstance().getAmplia().getCaId());
createOrderRequest.setValidityEnd(Util.getDateYearsFromNow(2));
createOrderRequest.setKind(CertificateKinds.PUBLIC_KEY);
PkiBrazilCertificateParameters parameters = new PkiBrazilCertificateParameters();
parameters.setName(request.getSubjectName());
parameters.setCpf(request.getCpf());
createOrderRequest.setParameters(parameters);
Order order = client.createOrder(createOrderRequest);
```
**PHP**
```php
use Lacuna\Amplia\CreateOrderRequest;
use Lacuna\Amplia\CertificateKinds;
use Lacuna\Amplia\PkiBrazilCertificateParameters;
$client = Util::getAmpliaClient();
$createOrderRequest = new CreateOrderRequest();
$createOrderRequest->caId = getConfig()['amplia']['caId'];
$createOrderRequest->validityEnd = Util::getDateYearsFromNow(2);
$createOrderRequest->kind = CertificateKinds::PUBLIC_KEY;
$parameters = new PkiBrazilCertificateParameters();
$parameters->name = $subjectName;
$parameters->cpf = $cpf;
$createOrderRequest->parameters = $parameters;
$order = $client->createOrder($createOrderRequest);
```
**Python**
```python
from amplia_client import CreateOrderRequest, CertificateKinds, PkiBrazilCertificateParameters
client = get_amplia_client()
req = CreateOrderRequest()
req.ca_id = current_app.config['AMPLIA_CA_ID']
req.validity_end = get_two_years_from_now_date()
req.kind = CertificateKinds.PUBLIC_KEY
parameters = PkiBrazilCertificateParameters()
parameters.name = request.form['subjectName']
parameters.cpf = request.form['cpf']
req.parameters = parameters
order = client.create_order(req)
```
Por HTTP, a demo de e-CPF envia este corpo para `POST /api/orders/pki-brazil` (dados pessoais mascarados). O cliente dela serializa os
enums como números (`"Format": 1`, `"Kind": 1`), enquanto a resposta os traz pelo nome (`"format": "PkiBrazil"`, `"kind": "PublicKey"`):
```json
{
"Parameters": {
"Format": 1,
"Name": "(DEMO) Diego Rocha Gomes",
"EmailAddress": "someone@example.com",
"Cpf": "***.***.***-**",
"BirthDate": "1988-12-06T00:00:00",
"OrganizationalUnits": [
"Lab"
],
"Organization": "Lacuna Software Demo",
"Country": "BR",
"PhoneNumber": "+55 ** *****-****"
},
"CAId": "00000000-0000-0000-0000-000000000000",
"Kind": 1,
"Validity": "365.00:00:00",
"IssuePendingApproval": false
}
```
## Certificados SSL/TLS
`SslCertificateParameters` traz `dnsNames`, `subjectName`, `keyUsages`, `extendedKeyUsages`, `qualifiedStatements` e
`organizationIdentifier`. Os certificados SSL/TLS do Amplia seguem as
diretrizes do CA/Browser Forum e aceitam nomes curinga (wildcard) e endereços IP além de nomes DNS comuns.
Os nomes DNS e o subject vêm do pedido, nunca do CSR: a demo de SSL/TLS define explicitamente os nomes DNS, o subject, os usos de chave
`DigitalSignature` e `KeyEncipherment` e o uso estendido `ServerAuth`. Ela envia este corpo para `POST /api/orders/ssl` e depois
[emite o certificado a partir de um CSR](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr):
```json
{
"Parameters": {
"Format": 2,
"DnsNames": [
"www.example.com"
],
"SubjectName": {
"Organization": "Lacuna Software Demo",
"CommonName": "www.example.com",
"OrganizationalUnits": [
"Lab"
]
},
"KeyUsages": [
3,
7
],
"ExtendedKeyUsages": [
1
]
},
"CAId": "00000000-0000-0000-0000-000000000000",
"Kind": 1,
"Validity": "90.00:00:00",
"IssuePendingApproval": false
}
```
A demo verifica os nomes DNS antes de chamar o Amplia (de 1 a 10 nomes, em minúsculas, `*` só como o rótulo inteiro mais à esquerda,
sem endereços IP) e traduz os códigos de erro `BadDnsName` e `BadCsr` do Amplia em mensagens próprias.
Esses limites são da demo: o próprio Amplia aceita endereços IP.
O `SslCertificateParameters` dos clientes Node.js e PHP só define `dnsNames`.
## Ler, alterar e excluir pedidos
| Operação | Endpoint |
|---|---|
| Ler um pedido | `GET /api/orders/{id}`; `GET /api/v2/orders/{id}` também devolve o `parameters` dele |
| Ler um pedido com os parâmetros do formato | `GET /api/orders/{format}/{id}`, por exemplo `GET /api/orders/pki-brazil/{id}` |
| Alterar um pedido | `PUT /api/orders/{format}/{id}` |
| Excluir um pedido | `DELETE /api/orders/{id}` |
| Listar pedidos | `GET /api/orders`, com `Q`, `Limit`, `Offset`, `Cursor` e `Order` (`Asc`, `Desc`) |
* O `status` de um pedido é `Pending`, `Locked` ou `Issued`, e `canEdit` e `canDelete` dizem se ele ainda pode ser alterado ou excluído;
um pedido já emitido não pode ser excluído.
* `keyMediaConstraints` lista as [mídias da chave](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr#mídia-da-chave) em que o pedido pode ser emitido.
* Pedidos bloqueados podem ser vistos e desbloqueados no dashboard, e a API tem `PUT /api/orders/{id}/unlock-request`.
TODO(docs): o que bloqueia um pedido e o que a requisição de desbloqueio faz.
## Próximos passos
* [Emitir na máquina do titular](https://docs.lacunasoftware.com/articles/amplia/integration/issue-on-user-machine)
* [Emitir a partir de um CSR](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr)
* [Emitir um arquivo PKCS #12](https://docs.lacunasoftware.com/articles/amplia/integration/pkcs12)
* [Certificados de atributo](https://docs.lacunasoftware.com/articles/amplia/integration/attribute-certificates)
# [Emitir na máquina do titular](https://docs.lacunasoftware.com/articles/amplia/integration/issue-on-user-machine)
Neste fluxo, o titular emite o próprio certificado na página de emissão do Amplia, que gera o par de chaves no computador dele com o
[Web PKI](https://docs.lacunasoftware.com/articles/web-pki): o seu sistema só cria o pedido e envia o titular para a página.
## Fluxo
1. O seu servidor [cria o pedido](https://docs.lacunasoftware.com/articles/amplia/integration/orders), com o telefone do titular nos parâmetros.
1. O seu servidor pede o link de emissão do pedido, `GET /api/orders/{id}/issue-link?returnUrl=…`, passando a URL para a qual o Amplia
volta depois de emitir o certificado. A resposta é o próprio link, uma string JSON como `"https://amplia.lacunasoftware.com/issue/{token}?returnUrl={value}"`.
1. O titular abre o link. A página de emissão do Amplia envia um código por SMS ao telefone dele, gera o par de chaves com o Web PKI e
emite o certificado.
1. O Amplia devolve o titular para a URL de retorno.
Os samples mostram uma página que redireciona o titular para o link:
**Node.js**
```js
const RETURN_URL = 'http://localhost:3000/issue-cert-user-amplia/complete';
let request = new CreateOrderRequest({
caId: CONFIG['caId'],
validityEnd: Util.getTwoYearsFromNowDate(),
kind: CertificateKinds.PUBLIC_KEY,
parameters: new PkiBrazilCertificateParameters({
name: req.body.subjectName,
cpf: req.body.cpf,
phoneNumber: req.body.phoneNumber
})
});
let order = await client.createOrder(request);
let link = await client.getOrderIssueLink(order.getId(), RETURN_URL);
res.render('issue-cert-user-amplia/redirect', {
redirectLink: link
});
```
**Java**
```java
private final String returnUrl = "http://localhost:60695/issue-cert-user-amplia/complete";
PkiBrazilCertificateParameters parameters = new PkiBrazilCertificateParameters();
parameters.setName(request.getSubjectName());
parameters.setCpf(request.getCpf());
parameters.setPhoneNumber(request.getPhoneNumber());
createOrderRequest.setParameters(parameters);
Order order = client.createOrder(createOrderRequest);
String redirectLink = client.getOrderIssueLink(order.getId(), returnUrl);
```
**PHP**
```php
$RETURN_URL = 'http://localhost:8000/issue-cert-user-amplia/complete.php';
$parameters = new PkiBrazilCertificateParameters();
$parameters->name = $subjectName;
$parameters->cpf = $cpf;
$parameters->phoneNumber = $phoneNumber;
$createOrderRequest->parameters = $parameters;
$order = $client->createOrder($createOrderRequest);
$redirectLink = $client->getOrderIssueLink($order->id, $RETURN_URL);
```
**Python**
```python
RETURN_URL = 'http://localhost:5000/issue-cert-user-amplia/complete'
parameters = PkiBrazilCertificateParameters()
parameters.name = request.form['subjectName']
parameters.cpf = request.form['cpf']
parameters.phone_number = request.form['phoneNumber']
req.parameters = parameters
order = client.create_order(req)
link = client.get_order_issue_link(order.id, RETURN_URL)
```
A página de retorno dos samples só avisa o titular de que o certificado foi emitido.
Para usar o certificado no seu sistema, leia o pedido quando o titular voltar e busque o certificado só depois que o `status` do pedido
for `Issued` e ele tiver um `certificateId`:
`GET /api/orders/{id}` e depois `GET /api/v2/certificates/{certificateId}`.
O Amplia também pode avisar o seu sistema quando o certificado for emitido, por um [webhook](https://docs.lacunasoftware.com/articles/amplia/integration#webhook).
## Proteja o link de emissão
* O link de emissão contém o segredo do pedido: quem o tiver pode emitir o certificado. Mantenha-o no servidor, nunca o registre no log
nem o mostre, e abra-o com um carregamento completo de página na mesma aba.
* Dê à URL de retorno um token à prova de adulteração e com prazo de validade que identifique o pedido, em vez do id puro do pedido; o
token da demo vale uma hora.
* A demo verifica, antes de criar o pedido, se o navegador do visitante executa o Web PKI, que não roda em celulares nem no Safari para
desktop: um pedido criado nesses ambientes nunca poderia ser concluído na página de emissão.
O Amplia também permite que os titulares emitam em celulares Android e iOS.
TODO(docs): como funciona a emissão no celular.
## Outras formas de entregar o link de emissão
Em vez de redirecionar o titular você mesmo, você pode fazer o Amplia enviar o link ou mostrar um QR code:
| Entrega | Endpoint | O que acontece |
|---|---|---|
| E-mail | `POST /api/orders/{id}/send-issue-email-request` | O Amplia envia ao endereço que está nos parâmetros do pedido (`emailAddress`) uma mensagem com um botão para o link de emissão e, caso o botão não funcione, o endereço da página de emissão e um código para digitar nela. |
| SMS | `POST /api/orders/{id}/send-issue-sms-request`, ou `POST /api/v2/orders/{id}/send-issue-sms-request` com um corpo `{"attemptNumber": …}` | O Amplia envia o link de emissão por SMS ao telefone que está nos parâmetros do pedido (`phoneNumber`). |
| QR code | `GET /api/orders/{id}/issue-qrcode-image` | Devolve uma imagem PNG de um QR code que leva à página de emissão. |
| | `GET /api/orders/{id}/issue-qrcode-data` | Devolve, como uma string JSON, a URL que o QR code contém, para você desenhar o código. |
| | `GET /api/orders/{id}/issue-qrcode-link` | Devolve um link para `GET /api/orders/{id}/issue-qrcode`, que serve o mesmo PNG sem a chave de API, para uma tag ` `. |
* O e-mail e o SMS mandam o link só para o titular: o seu sistema nunca o vê, então uma aplicação com o papel **Agente de Registro** pode
usá-los, enquanto o link de emissão e o QR code exigem um papel que possa emitir (veja [Criar uma aplicação e uma chave de API](https://docs.lacunasoftware.com/articles/amplia/integration#criar-uma-aplicação-e-uma-chave-de-api)).
* Os dois falham se o pedido já foi emitido (`OrderAlreadyFulfilled`). O e-mail exige um endereço de e-mail no pedido (`NoEmailAddress`)
e o envio de e-mail habilitado na instância (`EmailDisabled`); o SMS exige um telefone (`NoPhoneNumber`) e o SMS habilitado (`SmsDisabled`).
* O assunto do e-mail é "Link para emissão do seu certificado digital".
* O QR code contém um endereço `…/ic/1/{secret}` na instância (ou no gateway de QR code configurado nela), que redireciona para a página
de emissão. Como o link de emissão, ele permite que qualquer pessoa que o leia emita o certificado.
* Das bibliotecas cliente, só o cliente .NET encapsula esses endpoints (`SendIssueEmailAsync`, `SendIssueSmsAsync`,
`GetOrderIssueQRCodeDataAsync`, `GetOrderIssueQRCodeImageAsync`); com as outras, chame-os diretamente, com o header `X-Api-Key`.
* E-mails e SMS passam pelos provedores configurados na instância: veja [Configuração de envio de e-mails](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-email)
e [Configuração de envio de SMS](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-sms).
* Em uma instância on-premises, a configuração `Amplia__IssueProcedureConfirmationEnabled` desliga a confirmação por SMS durante a
emissão.
* TODO(docs): o que `attemptNumber` muda (a primeira versão do endpoint de SMS envia `0`).
## Veja também
* [Criar pedidos de certificado](https://docs.lacunasoftware.com/articles/amplia/integration/orders)
* [Emitir a partir de um CSR](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr), quando a chave é gerada no seu servidor ou na sua própria página
* [Erros](https://docs.lacunasoftware.com/articles/amplia/integration/errors)
**Demonstrações e exemplos**
- Demonstração: [Issue an e-CPF certificate](https://try.lacunasoftware.com/pt/amplia/issue-e-cpf-certificate)
- Issue a certificate with the key on the user's machine using Amplia: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/IssueCertUserAmpliaController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/issue-cert-user-amplia.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/issue-cert-user-amplia/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/issue_cert_user_amplia.py)
# [Emitir a partir de um CSR](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr)
Quando você mesmo gera o par de chaves, no seu servidor ou no navegador do usuário, você envia ao [Amplia](https://docs.lacunasoftware.com/articles/amplia) só uma requisição
de assinatura de certificado (CSR), e ele devolve o certificado.
## Fluxo
1. Gere o par de chaves e o CSR. Faça isso antes de criar o pedido, para que uma falha na chave não consuma um pedido.
1. [Crie o pedido](https://docs.lacunasoftware.com/articles/amplia/integration/orders). O subject do certificado vem dos parâmetros do pedido, nunca do CSR.
1. Emita o certificado: `POST /api/v2/certificates` com o id do pedido, o CSR e a [mídia da chave](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr#mídia-da-chave).
1. Mantenha a chave privada onde ela foi gerada, e o certificado junto dela.
Os samples geram a chave e o CSR com o [PKI Express](https://docs.lacunasoftware.com/articles/pki-express), depois emitem o certificado e guardam os dois no
servidor:
**Node.js**
```js
const {KeyGenerator} = require('pki-express');
const {KeyMedia } = require('amplia-client/lib/enums');
const keyGenerator = new KeyGenerator({
keySize: req.body.keySize,
genCsr: true
});
let genKey = await keyGenerator.generate();
let generatedKey = genKey.getKey();
let cert = await client.issueCertificate(order.getId(), genKey.getCsr(), KeyMedia.PC);
let certId = cert.getId();
// WARNING: It is highly RECOMMENDED to ENCRYPT the key before storing
// in your database.
StorageMock.storeSync(generatedKey, certId, '.json');
StorageMock.storeSync(cert.getContentRaw(), certId, '.cer');
```
**Java**
```java
KeyGenerator keyGenerator = new KeyGenerator();
keyGenerator.setKeySize(2048);
keyGenerator.setKeyFormat(KeyFormats.JSON);
keyGenerator.setGenCsr(true);
KeyGenerationResult genKey = keyGenerator.generate();
Certificate cert = client.issueCertificate(order.getId(), genKey.getCsr());
// WARNING: It is highly RECOMMENDED to ENCRYPT the key before storing in your database.
StorageMock.storeKey(genKey.getKey().getBytes(), ".json", cert.getId());
StorageMock.storeKey(cert.getContent(), ".cer", cert.getId());
```
**PHP**
```php
use Lacuna\PkiExpress\KeyGenerator;
use Lacuna\PkiExpress\SupportedKeySizes;
use Lacuna\PkiExpress\KeyFormats;
$keyGenerator = new KeyGenerator();
$keyGenerator->keySize = SupportedKeySizes::S2048;
$keyGenerator->keyFormat = KeyFormats::JSON;
$keyGenerator->genCsr = true;
$genKey = $keyGenerator->generate();
$cert = $client->issueCertificate($order->getId(), $genKey->csr);
StorageMock::storeKey($genKey->key, ".json", $cert->id);
StorageMock::storeKey($cert->getContentRaw(), ".cer", $cert->id);
```
**Python**
```python
from pkiexpress import KeyGenerator, Pkcs12Generator, SupportedKeySizes
key_generator = KeyGenerator()
key_generator.key_size = SupportedKeySizes.S2048
key_generator.gen_csr = True
gen_key = key_generator.generate()
cert = client.issue_certificate(order.id, gen_key.csr)
pkcs12_generator = Pkcs12Generator()
pkcs12_generator.key = gen_key.key
pkcs12_generator.set_cert_file_from_base64(cert.content_base64)
pkcs12_gen_result = pkcs12_generator.generate('1234')
```
* Os samples alertam que a chave privada deve ser criptografada antes de ser guardada em um banco de dados.
* O sample em Python depois junta a chave e o certificado em um arquivo PKCS #12 com o PKI Express, para assinar com ele mais tarde.
Para que o Amplia gere a chave e devolva o arquivo PKCS #12, veja [Emitir um arquivo PKCS #12](https://docs.lacunasoftware.com/articles/amplia/integration/pkcs12).
## A chamada de emissão
`POST /api/v2/certificates` recebe um `IssueCertificateRequest`:
| Campo | Significado |
|---|---|
| `orderId` | Id do pedido a atender. |
| `csr` | O CSR. A demo de SSL/TLS o envia codificado em PEM. |
| `keyMedia` | Onde a chave fica; veja [Mídia da chave](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr#mídia-da-chave). |
| `keyId` | Id de uma chave. |
TODO(docs): a que se refere `keyId`.
A resposta é um `CertificateModel2` com o `id` do certificado, o `content` dele (em Base64), datas, número de série e, em `info`, o
subject, o emissor, os pontos de distribuição de LCR e as URIs de OCSP. `POST /api/certificates` recebe o mesmo corpo e devolve o
`CertificateModel` mais antigo.
A demo de e-CNPJ envia este corpo, com o enum como número:
```json
{
"OrderId": "00000000-0000-0000-0000-000000000000",
"Csr": "",
"KeyMedia": 1
}
```
Para ler o certificado depois, use `GET /api/v2/certificates/{id}` (com `fillContent=true` para incluir `content`) ou baixe-o de
`GET /api/certificates/{id}/content`.
TODO(docs): o formato do arquivo devolvido por `GET /api/certificates/{id}/content`.
### Mídia da chave
`keyMedia` é `PC`, `CryptoDevice`, `Mobile` ou `Cloud`.
* Uma chave gerada no seu servidor, ou pelo Web PKI no navegador do usuário, no computador dele: `PC`.
* Uma chave gerada pelo Web PKI em um token ou smart card: `CryptoDevice`.
O `keyMediaConstraints` de um pedido lista as mídias da chave que ele aceita.
Só o cliente Node.js envia `keyMedia`: o enum `KeyMedia` dele vem de `amplia-client/lib/enums`, já que o ponto de entrada do pacote não o
exporta. O cliente Java envia o id do pedido, o CSR e, opcionalmente, `keyId`; os clientes PHP e Python enviam só o id do pedido e o CSR.
TODO(docs): que mídia da chave o Amplia registra quando a requisição não traz nenhuma.
TODO(docs): quando usar `Mobile` e `Cloud` (o Amplia guarda ele mesmo as chaves dos certificados em nuvem, veja [gerenciamento de certificados em nuvem](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-cert-management)).
## Gerar a chave no navegador do usuário
Com o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki), a chave pode ser gerada no computador ou no token do usuário pela sua própria página, e só o CSR
chega ao seu servidor. O Web PKI devolve o id da chave junto com o CSR: depois da emissão, passe o id de volta para
[`importCertificate`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#importcertificate) (ou
[`importTokenCertificate`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#importtokencertificate))
para instalar o certificado junto da chave.
O par de chaves em si vem de
[`generateSoftwareRsaKeyPair`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#generatesoftwarersakeypair)
ou de [`generateTokenRsaKeyPair`](https://docs.lacunasoftware.com/content/typedocs/web-pki/classes/_lacuna_web_pki_d_.lacunawebpki.html#generatetokenrsakeypair).
TODO(docs): nenhum artigo ou sample mostra a geração de chave do Web PKI junto com o Amplia; para que a própria página do Amplia faça
isso, veja [Emitir na máquina do titular](https://docs.lacunasoftware.com/articles/amplia/integration/issue-on-user-machine).
## Certificados SSL/TLS
A demo de SSL/TLS gera uma chave RSA de 2048 bits e um CSR no servidor dela, cria um [pedido SSL](https://docs.lacunasoftware.com/articles/amplia/integration/orders#certificados-ssltls) e emite o
certificado com a mídia da chave `PC`; os visitantes também podem trazer o próprio CSR.
* Ela verifica o CSR do usuário antes de chamar o Amplia: no máximo 16 KB, em PEM, Base64 ou DER, uma chave RSA de 2048 a 4096 bits ou
uma chave EC em P-256 ou P-384, e uma assinatura que confere. Ela envia ao Amplia a requisição recodificada em PEM.
* O que ela devolve depende de quem gerou a chave: com uma chave do servidor, um .pfx, a chave privada em PEM e um .crt com o certificado
e a AC emissora, nunca a raiz; com o CSR do usuário, só o .crt. Ela mantém as chaves só em memória.
## Emitir um e-CPF depois de uma verificação facial
A demo de e-CPF com o rosto verifica a identidade do titular antes da emissão: o Datavalid do Serpro compara o rosto dele com a foto da
CNH, pelo Lacuna Bio no [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core). Depois que o rosto é aprovado, ela emite o e-CPF a partir de um CSR,
como no fluxo desta página, sem a página de emissão do Amplia e sem retorno.
Para a captura do rosto, veja o [cadastro biométrico](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/enrollment) e a
[autenticação facial](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/bio-sessions/authentication) do Rest PKI Core.
* A demo faz a verificação `pf-basica` do Datavalid antes de qualquer captura do rosto: o nome precisa conferir exatamente, a data de
nascimento conferir, o CPF estar regular e existir uma CNH.
* Ela cadastra o rosto com os dígitos do CPF como identificador do titular (subject identifier) e `VerifyFaceIdentityExternally = true`;
com `BioSubjectAlreadyEnrolled`, ela inicia uma autenticação facial em vez disso.
* O resultado do Datavalid não vem no status de conclusão: a demo o lê com `GetSessionDetailsAsync(sessionId)`, que exige uma chave de
API com permissão para ver sessões biométricas.
TODO(docs): nenhuma sequência de chamadas foi gravada para essa demo, e nenhum artigo cobre a verificação do Datavalid.
## Veja também
* [Criar pedidos de certificado](https://docs.lacunasoftware.com/articles/amplia/integration/orders)
* [Emitir um arquivo PKCS #12](https://docs.lacunasoftware.com/articles/amplia/integration/pkcs12)
* [Erros](https://docs.lacunasoftware.com/articles/amplia/integration/errors)
**Demonstrações e exemplos**
- Demonstração: [Issue an e-CNPJ certificate](https://try.lacunasoftware.com/pt/amplia/issue-e-cnpj-certificate)
- Demonstração: [Issue an SSL/TLS certificate](https://try.lacunasoftware.com/pt/amplia/issue-ssl-tls-certificate)
- Demonstração: [Issue an e-CPF with your face](https://try.lacunasoftware.com/pt/amplia/issue-an-ecpf-with-your-face)
- Issue a certificate with the key on the server using Amplia: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/IssueCertServerAmpliaController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/issue-cert-server-amplia.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/issue-cert-server-amplia/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/issue_cert_server_amplia.py)
# [Emitir um arquivo PKCS #12](https://docs.lacunasoftware.com/articles/amplia/integration/pkcs12)
Quando o seu sistema não tem uma chave própria para enviar, o [Amplia](https://docs.lacunasoftware.com/articles/amplia) pode gerar o par de chaves, emitir o certificado e
devolver os dois em um arquivo PKCS #12 (.pfx) protegido por senha. Essa API existe desde o Amplia 4.1.0.
## Fluxo
1. [Crie o pedido](https://docs.lacunasoftware.com/articles/amplia/integration/orders).
1. Chame `POST /api/certificates/pkcs12` com o id do pedido, a senha do arquivo e o tamanho da chave.
1. Guarde o arquivo a partir do `pfxContent` da resposta (em Base64); o `model` da resposta descreve o certificado emitido.
O único sample é em Node.js. Ele pede uma chave de 2048 bits, com a senha que o usuário digitou:
```js
let order = await client.createOrder(request);
let cert = await client.issuePkcs12Certificate(order.getId(), req.body.senha, 2048);
let certId = cert.getModel().id;
StorageMock.storeSync(cert.getPfxContent(), certId, '.pfx');
```
| Campo | Significado |
|---|---|
| `orderId` | Id do pedido a atender. |
| `password` | Senha que protege o arquivo PKCS #12. |
| `keySize` | Tamanho do par de chaves, em bits; o sample usa 2048. |
* Só o cliente Node.js tem essa chamada. O `getPfxContent()` dele devolve o `pfxContent` como a API o envia, em Base64, e `getModel()`
devolve o JSON do certificado sem alterações.
* TODO(docs): que tamanhos de chave são aceitos e se o Amplia guarda uma cópia da chave privada depois de devolver o arquivo.
* A chave privada chega ao seu servidor dentro do arquivo: proteja o arquivo e a senha como protegeria a própria chave.
## Chave e certificado em outros formatos
Desde o Amplia 4.7.0, `POST /api/certificates/software` recebe a mesma requisição e devolve a chave e o certificado em vários formatos:
`pkcs12Der`, `certificateDer` e `certificatePem`, `publicKeyDer` e `publicKeyPem`, `privateKeyPkcs8Der` e `privateKeyPkcs8Pem`,
`privateKeyPkcs1Der` e `privateKeyPkcs1Pem`, além do `model` do certificado.
O `issueSoftwareCertificate(orderId, password, keySize)` do cliente Node.js chama esse endpoint, com uma chave de 2048 bits por padrão.
## Veja também
* [Criar pedidos de certificado](https://docs.lacunasoftware.com/articles/amplia/integration/orders)
* [Emitir a partir de um CSR](https://docs.lacunasoftware.com/articles/amplia/integration/issue-from-csr), para manter a chave onde ela foi gerada
* [Erros](https://docs.lacunasoftware.com/articles/amplia/integration/errors)
**Demonstrações e exemplos**
- Issue a PKCS #12 certificate using Amplia: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/issue-pkcs12-cert-server-amplia.js)
# [Certificados de atributo](https://docs.lacunasoftware.com/articles/amplia/integration/attribute-certificates)
Um certificado de atributo não vincula um par de chaves ao titular: ele atesta uma característica do titular, como uma identidade
profissional (*PrivateID*) ou uma Carteira de Identificação Estudantil (CIE).
## Antes de começar
Certificados de atributo são emitidos por uma AC configurada para emiti-los, que não é a AC que emite certificados de chave pública: os
samples guardam o id dela em uma configuração separada, `attributeCaId`. Os operadores podem criar essa AC no dashboard.
TODO(docs): nenhum artigo explica como criar uma AC para certificados de atributo.
## Fluxo
1. [Crie o pedido](https://docs.lacunasoftware.com/articles/amplia/integration/orders) na AC de atributo, com `kind` igual a `Attribute` e os parâmetros do formato do certificado.
1. Emita o certificado sem CSR, já que um certificado de atributo não tem par de chaves próprio.
1. Guarde o conteúdo do certificado.
| Formato | Endpoint do pedido | Parâmetros obrigatórios |
|---|---|---|
| Carteira de Identificação Estudantil (CIE) | `POST /api/orders/cie` | `name`, `registrationNumber`, `degree`, `course` e `institution` (`name`, `city`, `state`) |
| PrivateID | `POST /api/orders/private-id` | `name`, `cpf`, `idNumber` |
Um pedido CIE também recebe `eea`, `cpf`, `birthDate`, `idNumber`, `idIssuer` e `idIssuerState`.
Desde o Amplia 4.15.0, os operadores também podem criar pedidos CIE em um formulário do dashboard.
TODO(docs): o que significam os campos de CIE e PrivateID (por exemplo, `eea`).
## Exemplo
O único sample é em PHP. Ele cria um pedido CIE e emite o certificado com um CSR `null`:
```php
use Lacuna\Amplia\CreateOrderRequest;
use Lacuna\Amplia\CertificateKinds;
use Lacuna\Amplia\CieCertificateParameters;
use Lacuna\Amplia\CieInstitution;
$client = Util::getAmpliaClient();
$createOrderRequest = new CreateOrderRequest();
$createOrderRequest->caId = getConfig()['amplia']['attributeCaId'];
$createOrderRequest->validityEnd = Util::getDateYearsFromNow(2);
$createOrderRequest->kind = CertificateKinds::ATTRIBUTE;
$parameters = new CieCertificateParameters();
$parameters->name = $name;
$parameters->cpf = $cpf;
$parameters->eea = $eea;
$parameters->degree = $degree;
$parameters->course = $course;
$parameters->registrationNumber = $registrationNumber;
$institution = new CieInstitution();
$institution->name = $institutionName;
$institution->city = $institutionCity;
$institution->state = $institutionState;
$parameters->institution = $institution;
$createOrderRequest->parameters = $parameters;
$order = $client->createOrder($createOrderRequest);
$cert = $client->issueCertificate($order->getId(), null);
$fileId = StorageMock::store($cert->getContentRaw(), '.ac');
```
O cliente PHP envia o CSR `null` para `POST /api/v2/certificates`, como para qualquer outro certificado; nenhuma das bibliotecas cliente
tem um método próprio para certificados de atributo.
Um certificado de atributo não tem extensão de arquivo padrão: o sample o guarda como `.ac`.
## Usar o certificado
O [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk) decodifica, valida e emite certificados de atributo X.509 em aplicações .NET.
Certificados de atributo podem ser [revogados](https://docs.lacunasoftware.com/articles/amplia/integration/revocation) como qualquer outro.
## Veja também
* [Criar pedidos de certificado](https://docs.lacunasoftware.com/articles/amplia/integration/orders)
* [Revogar certificados](https://docs.lacunasoftware.com/articles/amplia/integration/revocation)
* [Erros](https://docs.lacunasoftware.com/articles/amplia/integration/errors)
**Demonstrações e exemplos**
- Issue an attribute certificate using Amplia: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/issue-cert-attribute-amplia/index.php)
# [Revogar certificados](https://docs.lacunasoftware.com/articles/amplia/integration/revocation)
## Revogar um certificado
`DELETE /api/certificates/{id}` revoga o certificado com esse id; a resposta não tem corpo. Depois da revogação, o `dateRevoked` do
certificado é preenchido.
As bibliotecas cliente o chamam com `revokeCertificate(certificateId)` (Python: `revoke_certificate`). Nem a API nem os clientes recebem
um motivo de revogação.
O cliente .NET o chama com `RevokeCertificateAsync(certificateId)`.
* Revogar exige um papel que possa excluir certificados, como **Operador** ou **Agente de Registro** (veja [Criar uma aplicação e uma chave de API](https://docs.lacunasoftware.com/articles/amplia/integration#criar-uma-aplicação-e-uma-chave-de-api)).
* O Amplia registra só a data da revogação, e a API não tem chamada para desfazê-la.
* Revogar um certificado que já está revogado falha com o código de erro `CertificateAlreadyRevoked`.
TODO(docs): nenhum sample mostra a revogação pela API.
No dashboard, usuários com o papel *Registration Agent* podem criar pedidos de certificado e revogar certificados, e nada mais.
## Como a revogação é publicada
* O Amplia publica LCRs nos [domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains) que você escolher.
O certificado emitido os lista em `info.crlDistributionPoints`.
* O Amplia pode responder a requisições [OCSP](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-ocsp) e pode [copiar LCRs e certificados de AC](https://docs.lacunasoftware.com/articles/amplia/on-premises/crl-propagation)
para um armazenamento externo.
* `GET /api/cas/{caId}/crls` lista as LCRs de uma AC, e `GET /api/crls/{id}/content` baixa uma delas.
Revogar não emite uma nova LCR: cada AC emite LCRs no seu próprio cronograma, a cada `crlIssuePeriod`, cada uma válida por
`crlIssueValidity` (os dois são campos da AC). Por isso, uma revogação aparece na próxima LCR programada da AC, em cerca de um
`crlIssuePeriod`. As entradas da LCR trazem o número de série e a data da revogação, sem código de motivo.
O respondedor OCSP do Amplia lê a revogação do banco de dados, então informa o certificado como revogado na hora, com o motivo
`unspecified`.
## Renovação
O Amplia pode renovar certificados, e o titular recebe um novo certificado no lugar do que está vencendo.
TODO(docs): nenhum artigo documenta como renovar um certificado, pela API ou de outra forma.
## Veja também
* [Erros](https://docs.lacunasoftware.com/articles/amplia/integration/errors)
* [Configuração de OCSP](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-ocsp)
* [Propagação de LCRs](https://docs.lacunasoftware.com/articles/amplia/on-premises/crl-propagation)
# [Erros](https://docs.lacunasoftware.com/articles/amplia/integration/errors)
## Códigos de status HTTP
A API do [Amplia](https://docs.lacunasoftware.com/articles/amplia) responde com um código de status HTTP:
| Status | Significado |
|---|---|
| 200 OK | A requisição foi processada; o corpo depende da operação. |
| 400 Bad Request | Erro de sintaxe, por exemplo, um campo obrigatório ausente. |
| 401 Unauthorized | A chave de API não foi enviada ou é inválida. |
| 403 Forbidden | A chave de API é válida, mas a aplicação não tem permissão para a operação. |
| 422 Unprocessable Entity | Erro de API, por exemplo, o id de uma entidade que não é encontrada; o corpo é um `ErrorModel`. |
| 500 Internal Server Error | Erro inesperado; o `exceptionCode` no corpo ajuda o suporte da Lacuna no diagnóstico. |
## O corpo do erro
Um `ErrorModel` tem `code`, `message` e `details`. As mensagens não seguem o header `Accept-Language`: registre-as no log em vez de
mostrá-las aos usuários.
A demo de e-CPF mostra aos usuários o código de erro, nunca a mensagem, e trata as respostas 4xx do Amplia como recusas e qualquer outra
coisa como falha.
## O que as bibliotecas cliente lançam
| Cliente | Erro de API (422) | Outros erros HTTP | Endpoint inacessível |
|---|---|---|---|
| Node.js | `AmpliaError`, cujo `code` é o código de erro; `OrderLocketError` quando o código é `OrderLocked` | `RestError`, cujo `code` é o status HTTP | `RestUnreachableError` |
| Java | `RestErrorException`, com o status e a mensagem, mas sem o código de erro | `RestErrorException`, ou `RestResourceNotFoundException` em algumas respostas 404 | `RestUnreachableException` |
| PHP | `AmpliaException` (`getErrorCode()`, `getErrorMessage()`); `OrderLockedException` quando o código é `OrderLocked` | `RestErrorException` (`getStatusCode()`) | `RestUnreachableException` |
| Python | `RestError`, só com o status HTTP (veja abaixo) | `RestError` | `RestUnreachableError` |
| .NET | `AmpliaException` (`Code`, `CodeStr`, `ErrorMessage`); `OrderLockedException` quando o código é `OrderLocked` | `RestException` do `Lacuna.RestClient` | |
O cliente Python 1.0.0 define `AmpliaError` e `OrderLockedError` para respostas 422, mas o tratamento de erros dele lê o código do objeto
errado, então toda resposta de erro vira `RestError` só com o status HTTP.
## Códigos de erro
O enum `ErrorCodes` da API, que o cliente .NET expõe como `AmpliaException.Code`, tem 160 códigos no Amplia 5.0.1; os clientes Node.js e
PHP listam 95 deles (`AmpliaErrorCodes` no Node.js, `ErrorCodes` no PHP).
Estes são os que as chamadas deste guia devolvem, com o que o Amplia 5.0.1 quer dizer com cada um:
| Área | Código | Significado |
|---|---|---|
| Pedidos | `OrderNotFound` | Não há pedido com esse id (ou, na página de emissão, com esse segredo). |
| | `OrderDeleted` | O pedido foi excluído. |
| | `OrderAlreadyFulfilled` | O certificado do pedido já foi emitido: ele não pode ser emitido de novo, nem excluído, nem ter o link enviado. |
| | `OrderLocked` | O pedido está bloqueado. |
| | `CARequired` | Nenhum `caId` foi enviado e a aplicação não tem AC padrão. |
| | `CANotFound`, `TemplateNotFound` | Não há AC, ou modelo, com esse id. |
| | `CANotActive` | A AC não está ativa, então não pode emitir. |
| | `NoSuitableTemplateFound` | A conta não tem modelo para o formato do pedido. |
| | `ValidityRequired`, `KindRequired` | Um pedido sem modelo precisa de `validityEnd` e `kind`; um pedido em um modelo sem validade padrão precisa de `validityEnd`. |
| | `ValidityStartRequired`, `BadValidityStart` | Um pedido com `issuePendingApproval` precisa de um `validityStart` no futuro. |
| | `TemplateForbidsCustomValidity`, `TemplateForbidsParameter` | O modelo não deixa o pedido definir a validade, ou esse parâmetro. |
| | `CertificateKindNotSupported` | O `kind` (chave pública ou atributo) não corresponde ao que a AC ou o modelo emite. |
| | `BadOrderParameters` | Falta um parâmetro, ou o modelo não o permite. |
| | `InvalidCpf`, `InvalidCnpj`, `InvalidPhoneNumber` | O CPF, o CNPJ ou o telefone não é válido. |
| | `DnsNamesRequired`, `BadDnsName` | Um pedido SSL/TLS não tem nome DNS, ou tem um que não é válido. |
| Emissão | `CsrRequired` | Um certificado de chave pública precisa de um CSR (ou de uma chave guardada no Amplia). |
| | `CsrNotExpected` | Um certificado de atributo não recebe CSR nem chave. |
| | `BadCsr` | O CSR não é válido. |
| | `KeySizeNotSupported`, `BadKeySize` | O tamanho de chave não é válido, ou é menor que o mínimo do modelo. |
| | `DigestAlgorithmNotSupported` | O algoritmo de hash não é suportado. |
| Link de emissão | `NoEmailAddress`, `EmailDisabled` | O pedido não tem endereço de e-mail, ou a instância não pode enviar e-mail. |
| | `NoPhoneNumber`, `SmsDisabled` | O pedido não tem telefone, ou a instância não pode enviar SMS. |
| | `IssueSessionExpired`, `NoMoreConfirmAttemptsRemaining` | Durante a emissão na página de emissão do Amplia: a sessão expirou, ou o titular esgotou as tentativas de digitar o código enviado por SMS. |
| Certificados | `CertificateNotFound` | Não há certificado com esse id. |
| | `CertificateAlreadyRevoked` | O certificado já foi revogado. |
Uma permissão que falta à aplicação é respondida com 403 Forbidden, e não com um código de erro: no Amplia 5.0.1, nenhuma chamada devolve
os códigos `AccessDeniedTo…` que os clientes listam.
A demo de SSL/TLS traduz `BadDnsName` e `BadCsr` em mensagens próprias.
## Veja também
* [Guia de integração](https://docs.lacunasoftware.com/articles/amplia/integration)
* [Referência rápida do Amplia](https://docs.lacunasoftware.com/articles/amplia/quick-reference#erros)
# [Amplia *on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises)
O [Amplia](https://docs.lacunasoftware.com/articles/amplia) pode ser utilizado em nuvem (SaaS) ou você pode ter uma instância na sua própria infraestrutura ("on premises").
## Ambientes suportados
O Amplia é compatível com os seguintes ambientes:
* [Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows)
* [Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux)
* [Docker](https://docs.lacunasoftware.com/articles/amplia/on-premises/docker)
* [Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure)
Clique em um dos ambientes acima para obter instruções específicas de instalação e atualização.
## Planejamento antes da instalação
Antes de você começar, você deve planejar com antecedência onde pretende armazenar suas chaves da Autoridade Certificadora (CA) e onde pretende publicar suas Listas de Certificados Revogados (LCRs).
Por favor leia as seções abaixo para detalhes de cada uma das decisões.
### Armazenamento de chaves
As chaves das suas Autoridades Certificadoras (ACs) podem ser armazenadas em uma variedade de locais, chamados *repositórios de chaves* (key stores). A escolha de onde armazenar as chaves das ACs é uma das mais importantes
decisões ao se planejar a implantação de uma AC. Veja o artigo [Repositórios de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores) para mais informações sobre onde guardar as chaves de AC e como configurar
o Amplia de acordo.
### Domínio de acesso ao painel de controle / Certificado SSL
A aplicação tem um painel de controle para gerenciar ACs, chaves etc. Você deve escolher um domínio para acessá-lo, por exemplo `ac.patorum.com`.
O domínio escolhido deve ser criado no servidor de DNS da zona (registro A ou CNAME) antes da instalação, apontando para o servidor onde o Amplia será instalado.
> **Observação**
>
> Não há suporte para o uso de um diretório virtual (subpasta) em um domínio que hospeda outro site, por exemplo *www.patorum.com/ac*
É também recomendado que você tenha um **certificado SSL válido** para o domínio escolhido.
### Publicação de LCRs (domínios de acesso)
Certificados emitidos pelo Amplia incluem a extensão X.509 *CRL Distribution Point*, que contém links para endereços online onde um terceiro que deseje validar um certificado deve obter a Lista de Certificados Revogados (LCR) mais recente da AC,
etapa necessária para determinar o status de revogação do certificado.
Estes links possuem o seguinte formato: `http://seu-dominio-de-acesso/crls/sua-ac.crl`
A parte do link `seu-dominio-de-acesso` é chamada na configuração Amplia de um *domínio de acesso*. Você deve escolher pelo menos um domínio de acesso, mas é recomendado ter dois domínios de acesso,
preferencialmente um independente do outro, por exemplo:
* *ac.patorum.com*
* *ac.patorum.net*
> **Dica**
>
> Se a sua instância do Amplia for ficar disponível publicamente, um dos domínios de acesso pode ser o mesmo domínio onde o painel de controle do Amplia será acessado.
> Caso não tenha certeza se sua instância ficará ou não disponível publicamente, escolha domínios de acesso diferentes do domínio do painel de controle.
> **Observação**
>
> Não há suporte para o uso de um diretório virtual (subpasta) em um domínio que hospeda outro site
Esses domínios devem ser escolhidos tendo em mente que eles **terão que ser mantidos por um longo período** (por toda a vida útil dos certificados emitidos em sua instância do Amplia, que normalmente é de vários anos).
Os domínios de acesso escolhidos devem ser criados nos servidores DNS (registros A ou CNAME) apontando para o servidor no qual o Amplia será instalado.
> **Dica**
>
> Você não precisa de um certificado SSL para seus domínios de acesso. Uma vez que o padrão X.509 recomenda que LCRs sejam distribuídas por HTTP em vez de HTTPS, os certificados
> são emitidos com links usando protocolo HTTP.
## Veja também
* [Preparando o banco de dados para instalação do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database)
* [Operando o Amplia sem ser *owner* do banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/unprivileged-db-user)
* [Verificando a versão instalada do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/check-version)
* [Atualizando o Amplia da versão 4.x para 5.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-50)
* [Atualizando o Amplia da versão 2.16 para 3.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-30)
* [Histórico de versões](https://docs.lacunasoftware.com/articles/amplia/changelog)
# [Amplia - Setup em Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows)
> **Observação**
>
> A documentação deste sistema encontra-se em construção. Pedimos desculpas pela inconveniência. Por favor contate-nos caso
> precise de alguma informação que ainda não está documentada.
## Veja também
* [Instalação do Amplia em Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/install)
* [Atualização do Amplia em Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot)
# [Instalação do Amplia em Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/install)
Para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) no Windows Server, siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/amplia/on-premises).
> **Dica**
>
> Antes de começar, leia a seção [Planejamento antes da instalação](https://docs.lacunasoftware.com/articles/amplia/on-premises#planning).
## Pré-requisitos
* Windows Server 2016 ou mais recente (qualquer edição)
* SQL Server 2016 ou mais recente (recomendada edição *Standard* ou superior)
* Licença PKI SDK (no formato Base64)
* Licença Web PKI (formato Base64/binário) -- necessário apenas se os usuários forem emitir certificados em seus computadores (procedimento de emissão na web)
* Entradas de DNS criadas anteriormente para:
* Acesso ao painel de controle (veja [Domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain))
* Publicação de LCRs (veja [Domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains))
* (recomendado) Certificado SSL válido para o domínio de acesso ao painel de controle
* **Connection string** para um [banco de dados SQL Server ou PostgreSQL criado previamente](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database)
## Instalação
1. Instale IIS
1. Instale o ASP.NET Core 10.0 Hosting Bundle ([página de download](https://dotnet.microsoft.com/download/dotnet/10.0) ou [download direto](https://aka.ms/dotnet/10.0/dotnet-hosting-win.exe))
1. Criar pasta para site
1. Criar pasta para logs
1. Criar site
* Não definir código gerenciado
* Definir o perfil do usuário de carregamento para `True`

1. Extrair arquivos.
## Configuração
Na pasta de instalação, renomeie o arquivo **appsettings.iis-template.json** para **appsettings.iis.json** (remova o sufixo `-template`). Então, edite o arquivo para configurar a instância do Amplia.
### *Connection string* do banco de dados
Na seção ***ConnectionStrings***, na configuração ***DefaultConnection***, defina a cadeia de conexão para o banco de dados criado anteriormente. Uma string de conexão típica é assim:
```text
Data Source=SERVER;Initial Catalog=DATABASE;User ID=USERNAME;Password=PASSWORD
```
> **Observação**
>
> Se você criou banco de dados usando recursos avançados como *log shipping* ou *mirroring*, sua *connection string* pode ser diferente.
### Logging
Na seção ***Serilog***, configure o registro do aplicativo:
```json
...
"Serilog": {
"MinimumLevel": {
"Default": "Warning",
},
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "C:\\Logs\\Amplia.log",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}",
}
}
],
}
...
```
Altere a configuração do ***path*** para outra pasta de log.
> **Observação**
>
> Lembre-se de conceder acesso total ao usuário do aplicativo na pasta de log.
### Configurações *General*
Na seção **General**:
* **SiteUrl**: URL publicamente acessível do site (ex.: `https://ca.patorum.com/`). Este endereço é usado para compor emails com links de volta ao site.
* **EncryptionKey**: chave 256 bits usada para criptografar segredos temporários emitidos pelo site e as chaves do [repositório de chaves em banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/database). Veja abaixo como gerar essa chave.
* **AutoUpdateDatabase**: Por padrão, o aplicativo tenta realizar alterações de modelo no banco de dados após uma atualização (quando necessário). Defina como `false` se o aplicativo não tiver
permissões de proprietário sobre o banco de dados.
* **SupportEmailAdress**: o endereço de e-mail de suporte (usado no rodapé dos e-mails enviados)
Para gerar a *EncryptionKey*, execute o seguinte em um Powershell:
```ps
$k = New-Object byte[] 32;
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($k);
[Convert]::ToBase64String($k);
```
## Iniciando o aplicativo
1. Iniciar o site
1. Acesse o site
## Veja também
* [Atualização do Amplia em Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot)
# [Resolução de problemas do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot)
Problemas comuns:
* Erro *[HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot/502-5)* ao tentar acessar o site
> **Observação**
>
> Se você está encontrando um problema não listado, tente [verificar os logs de sistema](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot/check-logs) ou entre em contato conosco.
## Veja também
* [Verificando os logs de sistema](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot/check-logs)
* [Diagnosticando problemas na inicialização](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot/startup-errors)
# [HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure (Windows Server)](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot/502-5)
Ao instalar ou atualizar o site, pode aparecer o erro *HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure*:

A principal causa para esse problema é o ASP.NET Core 10.0 Hosting Bundle, exigido pelo Amplia 5.0, não estar instalado ([baixe aqui](https://dotnet.microsoft.com/download/dotnet/10.0)).
Para verificar que o ASP.NET Core Runtime 10.0 está instalado, execute o comando abaixo em um command prompt ou no Powershell:
```sh
dotnet --list-runtimes
```
A saída deve conter dois itens com a versão **10.0.x**, por exemplo:
```text
Microsoft.AspNetCore.App 10.0.x [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.x [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
```
Embora essa seja a principal causa, existem diversos fatores que podem acarretar esse erro. Para diagnosticar o problema,
veja [Diagnosticando problemas na inicialização](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot/startup-errors).
# [Amplia - Verificando os logs de sistema no Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot/check-logs)
Por padrão, os logs de sistema ficam localizados no arquivo `C:\Logs\Amplia.log`.
Para conferir qual arquivo está sendo utilizado, verifique o arquivo `appsettings.iis.json` na pasta do site. Localize a seção `Serilog`, subseção `WriteTo`.
Exemplo:
```json
"Serilog": {
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "C:\\Logs\\Amplia.log",
}
}
],
},
```
Para acompanhar o log de maneira contínua, você pode abrir um Powershell e digitar o seguinte comando:
```ps
gc C:\Logs\Amplia.log -Tail 100 -Wait
```
> **Observação**
>
> Alguns erros, geralmente os que ocorrem durante a inicialização do sistema, podem não ser registrados no arquivo de log do sistema.
> Para diagnosticar tais erros, veja [Diagnosticando problemas na inicialização](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot/startup-errors).
# [Diagnosticando problemas na inicialização (Windows Server)](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot/startup-errors)
Alguns erros que ocorrem durante a inicialização do sistema impedem que o framework de log do Amplia funcione. Nesses casos, tente uma das soluções abaixo para diagnosticar o problema.
## 1. Logs do Windows
1. Abra o menu iniciar, pesquise por **Event Viewer**, selecione então o aplicativo **Event Viewer**.
1. Em **Event Viewer**, abra o nó **Windows Logs**.
1. Selecione **Application** para abrir os logs de aplicação.
1. Procure por erros associados com falha no sistema, por exemplo erros contendo *IIS AspNetCore Module* ou *IIS Express AspNetCore Module* na coluna *Source*.
## 2. Habilitar o log de stdout do ASP.NET Core
> **Info**
>
> Desative o log de stdout quando a solução de problemas for concluída.
Para habilitar o log de stdout ASP.NET Core:
1. Navegue até a pasta de instalação do site no servidor.
1. Se a pasta *logs* não estiver presente, crie a pasta.
1. Edite o arquivo *web.config*. Configure **stdoutLogEnabled** para `true` e mude o **stdoutLogFile** com caminho para o ponto da pasta com *logs* (por exemplo,`.\logs\stdout`). `stdout` no caminho é o prefixo do nome do arquivo de log. Um timestamp, id processo e extensão de arquivo são adicionadas automaticamente quando o log é criado. Usando `stdout` como o prefixo do nome do arquivo, um nome de arquivo típico seria *stdout_20180205184032_5412.log*.
1. Certifique-se de que o usuário do *application pool* tenha permissões de gravação na pasta de logs.
1. Salve o arquivo *web.config*.
1. Tente acessar o sistema.
1. Navegue pela pasta de *logs*. Encontre e abra o arquivo mais recente.
1. Estude o erro dos logs.
Para desabilitar após a solução de problemas estar completa:
1. Edite o arquivo *web.config*.
1. Configure **stdoutLogEnabled** para `false`.
1. Salve o arquivo.
> **Aviso**
>
> A falha em desativar o log de stdout pode levar a falhas no sistema ou no servidor. Não há limite no tamanho do arquivo de log ou no número de arquivos de log criados.
## Veja também
* [Troubleshoot ASP.NET Core on IIS](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/troubleshoot?view=aspnetcore-10.0)
# [Atualização do Amplia em Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/update)
Para atualizar a sua instância do [Amplia](https://docs.lacunasoftware.com/articles/amplia), siga as instruções dessa página.
> **Observação**
>
> Para saber se uma atualização demandará alteração nas configurações, observe no [Histórico de versões](https://docs.lacunasoftware.com/articles/amplia/changelog) as alterações
> de configuração entre a versão que você está instalando e a que encontra-se atualmente instalada (para saber como verificar a versão instalada
> do sistema, veja [este artigo](https://docs.lacunasoftware.com/articles/amplia/on-premises/check-version)).
> **Aviso**
>
> O Amplia 5.0 requer o ASP.NET Core 10.0 Hosting Bundle. Se você está atualizando a partir de uma versão 4.x, siga antes os passos de
> [Atualizando o Amplia da versão 4.x para 5.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-50).
1. Pare o site no IIS Manager
1. Crie um backup do banco de dados e da pasta do site
1. Extraia o conteúdo do novo pacote do Amplia ([amplia-5.0.0.zip](https://cdn.lacunasoftware.com/amplia/amplia-5.0.0.zip)) para a pasta do site, sobrescrevendo todos os arquivos
1. Faça eventuais alterações necessárias ao arquivo `appsettings.iis.json` (veja acima como saber quais alterações serão necessárias)
1. Inicie o site no IIS Manager
> **Observação**
>
> O arquivo `appsettings.iis.json` com as configurações da sua instância será preservado, pois no pacote não há nenhum arquivo com esse nome
## Veja também
* [Instalação do Amplia em Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/install)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/troubleshoot)
# [Setup do Amplia em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux)
O [Amplia](https://docs.lacunasoftware.com/articles/amplia) pode ser instalado nas seguintes distribuições de Linux:
* [Ubuntu Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-ubuntu)
* [Red Hat Enterprise Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-rhel)
* [Oracle Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-oracle)
* [Rocky Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-rocky)
> **Observação**
>
> Caso necessite suporte a uma distribuição específica, entre em contato conosco.
## Veja também
* [Atualização do Amplia em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot)
# [Instalação do Amplia em Oracle Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-oracle)
Para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) em Oracle Linux, siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/amplia/on-premises).
> **Dica**
>
> Antes de começar, leia a seção [Planejamento antes da instalação](https://docs.lacunasoftware.com/articles/amplia/on-premises#planning).
## Pré-requisitos
* Oracle Linux 8, 9 ou 10
* SQL Server 2016 ou mais recente (recomendada edição *Standard* ou superior)
* Licença PKI SDK (no formato Base64)
* Licença Web PKI (formato Base64/binário) -- necessário apenas se os usuários forem emitir certificados em seus computadores (procedimento de emissão na web)
* Entradas de DNS criadas anteriormente para:
* Acesso ao painel de controle (veja [Domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain))
* Publicação de LCRs (veja [Domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains))
* (recomendado) Certificado SSL válido para o domínio de acesso ao painel de controle
* **Connection string** para um [banco de dados SQL Server ou PostgreSQL criado previamente](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database)
## Instale o ASP.NET Core Runtime 10.0
> **Info**
>
> Estas instruções pressupõem que você está autenticado como **root**. Se não estiver, execute `sudo su -` antes de continuar!
Instale o pacote do ASP.NET Core Runtime:
```sh
yum install aspnetcore-runtime-10.0
```
Para testar a instalação, execute:
```sh
dotnet --list-runtimes
```
A saída esperada é semelhante a:
```text
Microsoft.AspNetCore.App 10.0.* [*/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.* [*/dotnet/shared/Microsoft.NETCore.App]
```
> **Dica**
>
> Para outras versões do sistema operacional e outras formas de instalar o ASP.NET Core Runtime, veja [esta página](https://docs.microsoft.com/en-us/dotnet/core/install/linux)
## Instalar o Amplia
> **Observação**
>
> Alguns passos utilizam o comando `nano`, que pode não estar disponível no seu sistema. Substitua o comando pelo
> `vi` ou instale o nano executando `yum install nano`.
Crie um usuário local para executar o servidor de aplicação do Amplia:
```sh
mkdir /var/amplia
useradd --system --home-dir /var/amplia amplia
chown amplia:amplia /var/amplia
```
Crie a pasta do site, baixe e extraia os binários:
```sh
mkdir /usr/share/amplia
curl -O https://cdn.lacunasoftware.com/amplia/amplia-5.0.0.tar.gz
tar xzf amplia-5.0.0.tar.gz -C /usr/share/amplia
chmod -R a=,u+rwX,go+rX /usr/share/amplia
```
> **Observação**
>
> Os arquivos do site podem ser lidos por qualquer usuário mas só podem ser alterados por usuários com permissões elevadas. Isso significa que o usuário da aplicação (*amplia*)
> pode ler os arquivos mas não pode alterá-los (isso é intencional).
Crie o arquivo de configuração do Amplia a partir do template fornecido:
```sh
mkdir /etc/amplia
cp /usr/share/amplia/config-templates/linux/appsettings.conf /etc/amplia/
chown -R root:amplia /etc/amplia
chmod -R a=,u+rwX,g+rX /etc/amplia
```
> **Observação**
>
> Arquivos de configuração só podem ser lidos por membros do grupo *amplia* e só podem ser alterados por usuários com permissões elevadas. Isso é importante para proteger informações
> sigilosas armazenadas no arquivo de configuração dos demais usuários da máquina.
## Configure o Amplia
Edite o arquivo de configuração para configurar sua instância do Amplia:
```sh
nano /etc/amplia/appsettings.conf
```
Na seção `[General]`, para preencher o parâmetro `EncryptionKey` gere uma chave de 256 bits para cifrar dados sensíveis armazenados no banco de dados:
```sh
openssl rand -base64 32
```
Ainda na seção `[General]`, para preencher o parâmetro `RootPasswordHash` escolha uma senha forte para o acesso de root e compute o *hash* da senha:
```sh
dotnet /usr/share/amplia/Lacuna.Amplia.Site.dll -- hash-root-pass
```
Preencha os demais parâmetros de acordo com as instruções presentes no arquivo de configuração.
## Configurar um *daemon*
Crie o arquivo de definição do serviço:
```sh
touch /etc/systemd/system/amplia.service
nano /etc/systemd/system/amplia.service
```
Digite o seguinte:
```ini
[Unit]
Description=Amplia
[Service]
WorkingDirectory=/usr/share/amplia
ExecStart=/usr/bin/dotnet Lacuna.Amplia.Site.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=amplia
User=amplia
Environment=ASPNETCORE_ENVIRONMENT=Linux
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
```
Salve o arquivo, habilite o serviço e inicie-o:
```sh
systemctl enable amplia
systemctl start amplia
systemctl status amplia
```
A saída esperada é semelhante a:
```text
* amplia.service - Amplia
Loaded: loaded (/etc/systemd/system/amplia.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2019-07-07 05:50:04 UTC; 4min 22s ago
Main PID: 10960 (dotnet)
Tasks: 31 (limit: 2319)
CGroup: /system.slice/amplia.service
└─10960 /usr/bin/dotnet Lacuna.Amplia.Site.dll
...
Dec 04 12:45:08 server.patorum.com amplia[32562]: Hosting environment: Production
Dec 04 12:45:08 server.patorum.com amplia[32562]: Content root path: /usr/share/amplia
Dec 04 12:45:08 server.patorum.com amplia[32562]: Now listening on: http://localhost:5000
Dec 04 12:45:08 server.patorum.com amplia[32562]: Application started. Press Ctrl+C to shut down.
Hint: Some lines were ellipsized, use -l to show in full.
```
Se necessário, reinicie o serviço: `systemctl restart amplia`
Para testar se o servidor do Amplia está rodando, execute:
```sh
curl http://localhost:5000/api/system/info
```
A saída esperada é algo como:
```json
{"productName":"Lacuna Amplia","productVersion":"5.x.x","spaVersion":"...","timestamp":"..."}
```
## Configurar um servidor proxy reverso
> **Observação**
>
> Se você preferir usar o Apache ao invés do Nginx, [veja este artigo](https://docs.microsoft.com/pt-br/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-2.2#configure-apache).
Instale o Nginx (se ainda não estiver instalado):
```sh
yum install nginx
systemctl enable nginx.service
systemctl start nginx.service
```
Teste se o Nginx está em execução:
```sh
curl -I http://localhost/
```
Verifique as primeiras linhas da saída, que devem ser semelhantes a:
```text
HTTP/1.1 200 OK
Server: nginx/...
...
```
Edite a configuração do Nginx:
```sh
nano /etc/nginx/nginx.conf
```
Remova ou comente (com `#`) toda a seção `server`, localizada logo abaixo da cláusula `include /etc/nginx/conf.d/*.conf;`. Depois de
comentar a seção, o arquivo de configuração deve ficar semelhante a este:
```nginx
...
http {
...
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
# server {
# listen 80 default_server;
# listen [::]:80 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
...
}
```
Crie um arquivo de configuração de site para o Amplia:
```sh
touch /etc/nginx/conf.d/amplia.conf
nano /etc/nginx/conf.d/amplia.conf
```
Digite o seguinte, substituindo o valor do item `server_name` pelo domínio de acesso ao painel de controle (veja [Domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain)):
```nginx
server {
listen 80;
server_name localhost ca.patorum.com;
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
> **Dica**
>
> Idealmente, a configuração do site deve conter as entradas `ssl_certificate` e `ssl_certificate_key` com o certificado SSL válido. Essa configuração está fora do escopo dessas
> instruções.
Permita que o Nginx acesse o serviço do Amplia:
```sh
setsebool -P httpd_can_network_connect on
```
Teste a configuração do Nginx e recarregue-a:
```sh
nginx -t
nginx -s reload
```
Teste o site:
```sh
curl http://localhost/api/system/info
```
Abra as portas HTTP e HTTPS do seu servidor para tráfego externo (caso ainda não estejam abertas):
```sh
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --zone=public --add-port=443/tcp --permanent
firewall-cmd --reload
```
## Veja também
* [Atualização do Amplia em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot)
# [Instalação do Amplia em Red Hat Enterprise Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-rhel)
Para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) em Red Hat Enterprise Linux, siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/amplia/on-premises).
> **Dica**
>
> Antes de começar, leia a seção [Planejamento antes da instalação](https://docs.lacunasoftware.com/articles/amplia/on-premises#planning).
## Pré-requisitos
> **Aviso**
>
> Antes de começar, certifique-se de que seu sistema esteja [registrado e inscrito no Red Hat Customer Portal](https://access.redhat.com/solutions/253273)
* Red Hat Enterprise Linux 8, 9 ou 10
* SQL Server 2016 ou mais recente (recomendada edição *Standard* ou superior)
* Licença PKI SDK (no formato Base64)
* Licença Web PKI (formato Base64/binário) -- necessário apenas se os usuários forem emitir certificados em seus computadores (procedimento de emissão na web)
* Entradas de DNS criadas anteriormente para:
* Acesso ao painel de controle (veja [Domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain))
* Publicação de LCRs (veja [Domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains))
* (recomendado) Certificado SSL válido para o domínio de acesso ao painel de controle
* **Connection string** para um [banco de dados SQL Server ou PostgreSQL criado previamente](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database)
## Instale o ASP.NET Core Runtime 10.0
> **Info**
>
> Estas instruções pressupõem que você está autenticado como **root**. Se não estiver, execute `sudo su -` antes de continuar!
Instale o pacote do ASP.NET Core Runtime:
```sh
yum install aspnetcore-runtime-10.0
```
> **Observação**
>
> Se o comando acima falhar, certifique-se de que o seu sistema está [registrado e associado a uma subscription da Red Hat](https://access.redhat.com/solutions/253273)
Para testar a instalação, execute:
```sh
dotnet --list-runtimes
```
A saída esperada é semelhante a:
```text
Microsoft.AspNetCore.App 10.0.* [*/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.* [*/dotnet/shared/Microsoft.NETCore.App]
```
> **Dica**
>
> Para outras versões do sistema operacional e outras formas de instalar o ASP.NET Core Runtime, veja [esta página](https://docs.microsoft.com/en-us/dotnet/core/install/linux)
## Instalar o Amplia
> **Observação**
>
> Alguns passos utilizam o comando `nano`, que pode não estar disponível no seu sistema. Substitua o comando pelo
> `vi` ou instale o nano executando `yum install nano`.
Crie um usuário local para executar o servidor de aplicação do Amplia:
```sh
mkdir /var/amplia
useradd --system --home-dir /var/amplia amplia
chown amplia:amplia /var/amplia
```
Crie a pasta do site, baixe e extraia os binários:
```sh
mkdir /usr/share/amplia
curl -O https://cdn.lacunasoftware.com/amplia/amplia-5.0.0.tar.gz
tar xzf amplia-5.0.0.tar.gz -C /usr/share/amplia
chmod -R a=,u+rwX,go+rX /usr/share/amplia
```
> **Observação**
>
> Os arquivos do site podem ser lidos por qualquer usuário mas só podem ser alterados por usuários com permissões elevadas. Isso significa que o usuário da aplicação (*amplia*)
> pode ler os arquivos mas não pode alterá-los (isso é intencional).
Crie o arquivo de configuração do Amplia a partir do template fornecido:
```sh
mkdir /etc/amplia
cp /usr/share/amplia/config-templates/linux/appsettings.conf /etc/amplia/
chown -R root:amplia /etc/amplia
chmod -R a=,u+rwX,g+rX /etc/amplia
```
> **Observação**
>
> Arquivos de configuração só podem ser lidos por membros do grupo *amplia* e só podem ser alterados por usuários com permissões elevadas. Isso é importante para proteger informações
> sigilosas armazenadas no arquivo de configuração dos demais usuários da máquina.
## Configure o Amplia
Edite o arquivo de configuração para configurar sua instância do Amplia:
```sh
nano /etc/amplia/appsettings.conf
```
Na seção `[General]`, para preencher o parâmetro `EncryptionKey` gere uma chave de 256 bits para cifrar dados sensíveis armazenados no banco de dados:
```sh
openssl rand -base64 32
```
Ainda na seção `[General]`, para preencher o parâmetro `RootPasswordHash` escolha uma senha forte para o acesso de root e compute o *hash* da senha:
```sh
dotnet /usr/share/amplia/Lacuna.Amplia.Site.dll -- hash-root-pass
```
Preencha os demais parâmetros de acordo com as instruções presentes no arquivo de configuração.
## Configurar um *daemon*
Crie o arquivo de definição do serviço:
```sh
touch /etc/systemd/system/amplia.service
nano /etc/systemd/system/amplia.service
```
Digite o seguinte:
```ini
[Unit]
Description=Amplia
[Service]
WorkingDirectory=/usr/share/amplia
ExecStart=/usr/bin/dotnet Lacuna.Amplia.Site.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=amplia
User=amplia
Environment=ASPNETCORE_ENVIRONMENT=Linux
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
```
Salve o arquivo, habilite o serviço e inicie-o:
```sh
systemctl enable amplia
systemctl start amplia
systemctl status amplia
```
A saída esperada é semelhante a:
```text
* amplia.service - Amplia
Loaded: loaded (/etc/systemd/system/amplia.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2019-07-07 05:50:04 UTC; 4min 22s ago
Main PID: 10960 (dotnet)
Tasks: 31 (limit: 2319)
CGroup: /system.slice/amplia.service
└─10960 /usr/bin/dotnet Lacuna.Amplia.Site.dll
...
Dec 04 12:45:08 server.patorum.com amplia[32562]: Hosting environment: Production
Dec 04 12:45:08 server.patorum.com amplia[32562]: Content root path: /usr/share/amplia
Dec 04 12:45:08 server.patorum.com amplia[32562]: Now listening on: http://localhost:5000
Dec 04 12:45:08 server.patorum.com amplia[32562]: Application started. Press Ctrl+C to shut down.
Hint: Some lines were ellipsized, use -l to show in full.
```
Se necessário, reinicie o serviço: `systemctl restart amplia`
Para testar se o servidor do Amplia está rodando, execute:
```sh
curl http://localhost:5000/api/system/info
```
A saída esperada é algo como:
```json
{"productName":"Lacuna Amplia","productVersion":"5.x.x","spaVersion":"...","timestamp":"..."}
```
## Configurar um servidor proxy reverso
> **Observação**
>
> Se você preferir usar o Apache ao invés do Nginx, [veja este artigo](https://docs.microsoft.com/pt-br/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-2.2#configure-apache).
Instale o Nginx (se ainda não estiver instalado):
```sh
yum install nginx
systemctl enable nginx.service
systemctl start nginx.service
```
Teste se o Nginx está em execução:
```sh
curl -I http://localhost/
```
Verifique as primeiras linhas da saída, que devem ser semelhantes a:
```text
HTTP/1.1 200 OK
Server: nginx/...
...
```
Edite a configuração do Nginx:
```sh
nano /etc/nginx/nginx.conf
```
Remova ou comente (com `#`) toda a seção `server`, localizada logo abaixo da cláusula `include /etc/nginx/conf.d/*.conf;`. Depois de
comentar a seção, o arquivo de configuração deve ficar semelhante a este:
```nginx
...
http {
...
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
# server {
# listen 80 default_server;
# listen [::]:80 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
...
}
```
Crie um arquivo de configuração de site para o Amplia:
```sh
touch /etc/nginx/conf.d/amplia.conf
nano /etc/nginx/conf.d/amplia.conf
```
Digite o seguinte, substituindo o valor do item `server_name` pelo domínio de acesso ao painel de controle (veja [Domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain)):
```nginx
server {
listen 80;
server_name localhost ca.patorum.com;
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
> **Dica**
>
> Idealmente, a configuração do site deve conter as entradas `ssl_certificate` e `ssl_certificate_key` com o certificado SSL válido. Essa configuração está fora do escopo dessas
> instruções.
Permita que o Nginx acesse o serviço do Amplia:
```sh
setsebool -P httpd_can_network_connect on
```
Teste a configuração do Nginx e recarregue-a:
```sh
nginx -t
nginx -s reload
```
Teste o site:
```sh
curl http://localhost/api/system/info
```
Abra as portas HTTP e HTTPS do seu servidor para tráfego externo (caso ainda não estejam abertas):
```sh
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --zone=public --add-port=443/tcp --permanent
firewall-cmd --reload
```
## Veja também
* [Atualização do Amplia em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot)
# [Instalação do Amplia em Rocky Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-rocky)
Para instalar uma instância [on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) em Rocky Linux, siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/amplia/on-premises).
> **Dica**
>
> Antes de começar, certifique-se de ter concluído os passos descritos em [Planejamento antes da instalação](https://docs.lacunasoftware.com/articles/amplia/on-premises#planning)
## Pré-requisitos
* Rocky Linux 8.x ou mais recente
* **Licença do PKI SDK** (no formato Base64)
* **Licença do Web PKI** (formato Base64/binário) -- necessária apenas se os usuários forem emitir certificados em seus computadores (procedimento de emissão na web)
* **Entradas de DNS** criadas previamente para:
* Acesso ao painel de controle (veja [Domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain))
* Publicação de LCRs (veja [Domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains))
* **Connection string** para um [banco de dados SQL Server ou PostgreSQL criado previamente](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database)
## Instale o ASP.NET Core Runtime 10.0
> **Info**
>
> Estas instruções pressupõem que você está autenticado como **root**. Se não estiver, execute `sudo su -` antes de continuar!
Instale o pacote do ASP.NET Core Runtime:
```sh
yum install aspnetcore-runtime-10.0
```
Para testar a instalação, execute:
```sh
dotnet --list-runtimes
```
A saída esperada é semelhante a:
```text
Microsoft.AspNetCore.App 10.0.* [*/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.* [*/dotnet/shared/Microsoft.NETCore.App]
```
> **Dica**
>
> Para outras versões do sistema operacional e outras formas de instalar o ASP.NET Core Runtime, veja [esta página](https://docs.microsoft.com/en-us/dotnet/core/install/linux)
## Instale o Amplia
Crie um usuário local para executar o servidor do Amplia:
```sh
mkdir /var/amplia
useradd --system --home-dir /var/amplia amplia
chown amplia:amplia /var/amplia
```
Crie a pasta do site, baixe e extraia os binários:
```sh
mkdir /usr/share/amplia
curl -O https://cdn.lacunasoftware.com/amplia/amplia-5.0.0.tar.gz
tar xzf amplia-5.0.0.tar.gz -C /usr/share/amplia
chmod -R a=,u+rwX,go+rX /usr/share/amplia
```
> **Observação**
>
> Os arquivos do site podem ser lidos por qualquer usuário, mas só podem ser alterados pelo usuário root. Isso significa que o usuário da aplicação (*amplia*) pode ler os arquivos, mas não pode alterá-los, o que é intencional.
Crie o arquivo de configuração a partir do template fornecido:
```sh
mkdir /etc/amplia
cp /usr/share/amplia/config-templates/linux/appsettings.conf /etc/amplia/
chown -R root:amplia /etc/amplia
chmod -R a=,u+rwX,g+rX /etc/amplia
```
> **Observação**
>
> Os arquivos de configuração só podem ser lidos por membros do grupo *amplia* e só podem ser alterados pelo usuário root. Isso é importante para proteger contra acessos não autorizados os dados sensíveis armazenados nos arquivos de configuração.
## Configure o Amplia
Edite o arquivo de configuração para configurar sua instância do Amplia:
```sh
nano /etc/amplia/appsettings.conf
```
Na seção `[General]`, para preencher a configuração `EncryptionKey`, gere uma chave de 256 bits para cifrar os dados sensíveis armazenados no banco de dados:
```sh
openssl rand -base64 32
```
Ainda na seção `[General]`, para preencher a configuração `RootPasswordHash`, escolha uma senha forte para o acesso root ao painel de controle e calcule o hash dela:
```sh
dotnet /usr/share/amplia/Lacuna.Amplia.Site.dll -- hash-root-pass
```
> **Observação**
>
> Se quiser habilitar o gerenciamento de usuários, deixe a configuração `RootPasswordHash` em branco e, em vez disso, siga os passos de [Configuração de OpenID Connect](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-oidc)
Preencha as demais configurações de acordo com as instruções do arquivo de configuração.
## Configure um daemon
Crie o arquivo de definição do serviço:
```sh
touch /etc/systemd/system/amplia.service
nano /etc/systemd/system/amplia.service
```
Digite o seguinte:
```ini
[Unit]
Description=Amplia
[Service]
WorkingDirectory=/usr/share/amplia
ExecStart=/usr/bin/dotnet Lacuna.Amplia.Site.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=amplia
User=amplia
Environment=ASPNETCORE_ENVIRONMENT=Linux
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
```
Salve o arquivo, habilite o serviço e inicie-o:
```sh
systemctl enable amplia
systemctl start amplia
systemctl status amplia
```
A saída esperada é semelhante a:
```text
* amplia.service - Amplia
Loaded: loaded (/etc/systemd/system/amplia.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2019-07-07 05:50:04 UTC; 4min 22s ago
Main PID: 10960 (dotnet)
Tasks: 31 (limit: 2319)
CGroup: /system.slice/amplia.service
└─10960 /usr/bin/dotnet Lacuna.Amplia.Site.dll
...
Dec 04 12:45:08 server.patorum.com amplia[32562]: Hosting environment: Production
Dec 04 12:45:08 server.patorum.com amplia[32562]: Content root path: /usr/share/amplia
Dec 04 12:45:08 server.patorum.com amplia[32562]: Now listening on: http://localhost:5000
Dec 04 12:45:08 server.patorum.com amplia[32562]: Application started. Press Ctrl+C to shut down.
Hint: Some lines were ellipsized, use -l to show in full.
```
Se necessário, reinicie o serviço: `systemctl restart amplia`
Para testar se o servidor do Amplia está em execução, execute:
```sh
curl http://localhost:5000/api/system/info
```
A saída esperada é algo como:
```json
{"productName":"Lacuna Amplia","productVersion":"5.x.x","spaVersion":"...","timestamp":"..."}
```
## Configure um servidor proxy reverso
> **Observação**
>
> Se preferir usar o Apache em vez do Nginx, [veja este artigo](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-2.2#configure-apache).
Instale o Nginx (se ainda não estiver instalado):
```sh
yum install nginx
systemctl enable nginx.service
systemctl start nginx.service
```
Teste se o Nginx está em execução:
```sh
curl -I http://localhost/
```
Verifique as primeiras linhas da saída, que devem ser semelhantes a:
```text
HTTP/1.1 200 OK
Server: nginx/...
...
```
Edite a configuração do Nginx:
```sh
nano /etc/nginx/nginx.conf
```
Remova ou comente (com `#`) toda a seção `server`, localizada logo abaixo da cláusula `include /etc/nginx/conf.d/*.conf;`. Depois de
comentar a seção, o arquivo de configuração deve ficar semelhante a este:
```nginx
...
http {
...
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
# server {
# listen 80 default_server;
# listen [::]:80 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
...
}
```
Crie um arquivo de configuração de site para o Amplia:
```sh
touch /etc/nginx/conf.d/amplia.conf
nano /etc/nginx/conf.d/amplia.conf
```
Digite o seguinte, substituindo o domínio de acesso ao painel de controle no item `server_name` (veja [Domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain)):
```nginx
server {
listen 80;
server_name localhost ca.patorum.com;
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
> **Dica**
>
> Idealmente, a configuração do site deve conter as entradas `ssl_certificate` e `ssl_certificate_key` com um certificado SSL válido. Essa configuração está fora do escopo destas instruções.
Permita que o Nginx acesse o serviço do Amplia:
```sh
setsebool -P httpd_can_network_connect on
```
Teste a configuração do Nginx e recarregue-a:
```sh
nginx -t
nginx -s reload
```
Teste o site:
```sh
curl http://localhost/api/system/info
```
Libere o tráfego HTTP e HTTPS no seu sistema (se ainda não estiver liberado):
```sh
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --zone=public --add-port=443/tcp --permanent
firewall-cmd --reload
```
## Veja também
* [Atualização do Amplia em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot)
# [Instalação do Amplia em Ubuntu Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-ubuntu)
Para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) em Ubuntu Server, siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/amplia/on-premises).
> **Dica**
>
> Antes de começar, leia a seção [Planejamento antes da instalação](https://docs.lacunasoftware.com/articles/amplia/on-premises#planning).
## Pré-requisitos
* Ubuntu Server 22.04, 24.04 ou 26.04 (as versões LTS suportadas pelo .NET 10; recomendamos a versão LTS mais recente)
* SQL Server 2016 ou mais recente (recomendada edição *Standard* ou superior)
* Licença PKI SDK (no formato Base64)
* Licença Web PKI (formato Base64/binário) -- necessário apenas se os usuários forem emitir certificados em seus computadores (procedimento de emissão na web)
* Entradas de DNS criadas anteriormente para:
* Acesso ao painel de controle (veja [Domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain))
* Publicação de LCRs (veja [Domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains))
* (recomendado) Certificado SSL válido para o domínio de acesso ao painel de controle
* **Connection string** para um [banco de dados SQL Server ou PostgreSQL criado previamente](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database)
## Instale o ASP.NET Core Runtime 10.0
> **Info**
>
> Essas instruções assumem que você está autenticado como **root**. Se você não estiver, execute `sudo su -` antes de continuar!
Instale o pacote `aspnetcore-runtime-10.0`. No Ubuntu 24.04 e 26.04, ele vem do repositório de pacotes do próprio Ubuntu. No Ubuntu 22.04,
adicione antes o repositório de *backports* do .NET para Ubuntu (isso só precisa ser feito uma vez por máquina).
### Ubuntu 26.04 e 24.04 (LTS)
```bash
apt-get update
apt-get install aspnetcore-runtime-10.0
```
### Ubuntu 22.04 (LTS)
```bash
add-apt-repository ppa:dotnet/backports
```
```bash
apt-get update
apt-get install aspnetcore-runtime-10.0
```
### Teste a instalação
Para testar a instalação, execute:
```sh
dotnet --list-runtimes
```
A saída esperada é semelhante a:
```text
Microsoft.AspNetCore.App 10.0.* [*/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.* [*/dotnet/shared/Microsoft.NETCore.App]
```
> **Dica**
>
> Para outras versões do sistema operacional e métodos alternativos de instalação do ASP.NET Core Runtime, visite [esta página](https://docs.microsoft.com/pt-br/dotnet/core/install/linux)
## Instalar o Amplia
Crie um usuário local para executar o servidor de aplicação do Amplia:
```sh
mkdir /var/amplia
useradd --system --home-dir /var/amplia amplia
chown amplia:amplia /var/amplia
```
Crie a pasta do site, baixe e extraia os binários:
```sh
mkdir /usr/share/amplia
curl -O https://cdn.lacunasoftware.com/amplia/amplia-5.0.0.tar.gz
tar xzf amplia-5.0.0.tar.gz -C /usr/share/amplia
chmod -R a=,u+rwX,go+rX /usr/share/amplia
```
> **Observação**
>
> Os arquivos do site podem ser lidos por qualquer usuário mas só podem ser alterados por usuários com permissões elevadas. Isso significa que o usuário da aplicação (*amplia*)
> pode ler os arquivos mas não pode alterá-los (isso é intencional).
Crie o arquivo de configuração do Amplia a partir do template fornecido:
```sh
mkdir /etc/amplia
cp /usr/share/amplia/config-templates/linux/appsettings.conf /etc/amplia/
chown -R root:amplia /etc/amplia
chmod -R a=,u+rwX,g+rX /etc/amplia
```
> **Observação**
>
> Arquivos de configuração só podem ser lidos por membros do grupo *amplia* e só podem ser alterados por usuários com permissões elevadas. Isso é importante para proteger informações
> sigilosas armazenadas no arquivo de configuração dos demais usuários da máquina.
## Configure o Amplia
Edite o arquivo de configuração para configurar sua instância do Amplia:
```sh
nano /etc/amplia/appsettings.conf
```
Na seção `[General]`, para preencher o parâmetro `EncryptionKey` gere uma chave de 256 bits para cifrar dados sensíveis armazenados no banco de dados:
```sh
openssl rand -base64 32
```
Ainda na seção `[General]`, para preencher o parâmetro `RootPasswordHash` escolha uma senha forte para o acesso de root e compute o *hash* da senha:
```sh
dotnet /usr/share/amplia/Lacuna.Amplia.Site.dll -- hash-root-pass
```
Preencha os demais parâmetros de acordo com as instruções presentes no arquivo de configuração.
## Configurar um *daemon*
Crie o arquivo de definição do serviço:
```sh
touch /etc/systemd/system/amplia.service
nano /etc/systemd/system/amplia.service
```
Digite o seguinte:
```ini
[Unit]
Description=Amplia
[Service]
WorkingDirectory=/usr/share/amplia
ExecStart=/usr/bin/dotnet Lacuna.Amplia.Site.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=amplia
User=amplia
Environment=ASPNETCORE_ENVIRONMENT=Linux
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
```
Salve o arquivo, habilite o serviço e inicie-o:
```sh
systemctl enable amplia
systemctl start amplia
systemctl status amplia
```
A saída esperada é semelhante a:
```text
* amplia.service - Amplia
Loaded: loaded (/etc/systemd/system/amplia.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2019-07-07 05:50:04 UTC; 4min 22s ago
Main PID: 10960 (dotnet)
Tasks: 31 (limit: 2319)
CGroup: /system.slice/amplia.service
└─10960 /usr/bin/dotnet Lacuna.Amplia.Site.dll
...
Dec 04 12:45:08 server.patorum.com amplia[32562]: Hosting environment: Production
Dec 04 12:45:08 server.patorum.com amplia[32562]: Content root path: /usr/share/amplia
Dec 04 12:45:08 server.patorum.com amplia[32562]: Now listening on: http://localhost:5000
Dec 04 12:45:08 server.patorum.com amplia[32562]: Application started. Press Ctrl+C to shut down.
Hint: Some lines were ellipsized, use -l to show in full.
```
Se necessário, reinicie o serviço: `systemctl restart amplia`
Para testar se o servidor do Amplia está rodando, execute:
```sh
curl http://localhost:5000/api/system/info
```
A saída esperada é algo como:
```json
{"productName":"Lacuna Amplia","productVersion":"5.x.x","spaVersion":"...","timestamp":"..."}
```
## Configurar um servidor proxy reverso
> **Observação**
>
> Se você preferir usar o Apache ao invés do Nginx, [veja este artigo](https://docs.microsoft.com/pt-br/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-2.2#configure-apache).
Instale o Nginx (se ainda não estiver instalado)
```sh
apt-get install nginx
```
Teste a instalação do Nginx:
```sh
curl -I http://localhost/
```
Verifique as primeiras linhas da saída, que devem ser similares a:
```text
HTTP/1.1 200 OK
Server: nginx/...
...
```
Desabilite o site padrão do Nginx:
```sh
rm /etc/nginx/sites-enabled/default
```
Crie um arquivo de configuração para o site do Amplia:
```sh
touch /etc/nginx/sites-available/amplia
nano /etc/nginx/sites-available/amplia
```
Digite o seguinte, substituindo o valor do item `server_name` pelo domínio de acesso ao painel de controle (veja [Domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain)):
```nginx
server {
listen 80;
server_name localhost ca.patorum.com;
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
> **Dica**
>
> Idealmente, a configuração do site deve conter as entradas `ssl_certificate` e `ssl_certificate_key` com o certificado SSL válido. Essa configuração está fora do escopo dessas
> instruções.
Ative o site:
```sh
ln -sf /etc/nginx/sites-available/amplia /etc/nginx/sites-enabled/amplia
```
Teste a configuração do Nginx e recarregue-a:
```sh
nginx -t
nginx -s reload
```
Teste o site:
```sh
curl http://localhost/api/system/info
```
## Veja também
* [Atualização do Amplia em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot)
# [Resolução de problemas do Amplia (Linux)](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot)
Problemas comuns:
* Erro *[HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot/502-5)* ao tentar acessar o site
> **Observação**
>
> Se você está encontrando um problema não listado, tente [verificar os logs de sistema](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot/check-logs) ou entre em contato conosco.
## Veja também
* [Verificando os logs de sistema](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot/check-logs)
# [HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure (Linux)](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot/502-5)
> **Observação**
>
> A documentação deste sistema encontra-se em construção. Pedimos desculpas pela inconveniência. Por favor contate-nos caso
> precise de alguma informação que ainda não está documentada.
# [Verificando os logs de sistema (Linux)](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot/check-logs)
Quanto instalado em Linux, o [Amplia](https://docs.lacunasoftware.com/articles/amplia) escreve seus logs no *systemd journal*, uma solução para gerenciamento de logs centralizada do Linux. Para
visualizar os logs, utiliza-se o utilitário `journalctl`.
Para verificar os logs do Amplia:
```sh
journalctl -u amplia
```
Utilize a flag `-f` para acompanhar o log de maneira contínua (como o comando `tail -f`):
```sh
journalctl -u amplia -f
```
Utilize o parâmetro `--since` para visualizar apenas os logs mais recentes:
```sh
journalctl -u amplia --since "1 min ago"
```
## Veja também
* [*How To Use Journalctl to View and Manipulate Systemd Logs*](https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs) (artigo da DigitalOcean)
* [Resolução de problemas em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot)
# [Atualização do Amplia em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/update)
Para atualizar sua [instância *on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia), siga os passos abaixo:
> **Info**
>
> Essas instruções assumem que você está autenticado como **root**. Se você não estiver, execute `sudo su -` antes de continuar!
> **Aviso**
>
> O Amplia 5.0 requer o ASP.NET Core Runtime 10.0. Se você está atualizando a partir de uma versão 4.x, siga os passos de
> [Atualizando o Amplia da versão 4.x para 5.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-50) antes de substituir os arquivos.
```sh
curl -O https://cdn.lacunasoftware.com/amplia/amplia-5.0.0.tar.gz
systemctl stop amplia
rm -fR /usr/share/amplia/*
tar xzf amplia-5.0.0.tar.gz -C /usr/share/amplia
chmod -R a=,u+rwX,go+rX /usr/share/amplia
systemctl start amplia
```
## Veja também
* [Setup do Amplia em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot)
# [Setup do Amplia em Docker](https://docs.lacunasoftware.com/articles/amplia/on-premises/docker)
Para instalar uma instância [on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) em Docker, siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/amplia/on-premises).
> **Dica**
>
> Antes de começar, certifique-se de ter concluído os passos descritos em [Planejamento antes da instalação](https://docs.lacunasoftware.com/articles/amplia/on-premises#planning)
Para o setup em Docker, a seguinte imagem é disponibilizada no Docker Hub:
**[Imagem Docker do Amplia](https://hub.docker.com/r/lacunasoftware/amplia)**
A imagem recomendada atualmente é `lacunasoftware/amplia:5.0`
Tags de imagem em destaque:
* A tag `5.0` aponta para a imagem 5.0.x mais recente **(recomendada)**
* A tag `5` aponta para a imagem 5.x mais recente
* A tag `stable` aponta para a imagem estável mais recente
> **Observação**
>
> Se você está atualizando uma instância que executa uma imagem 4.x, veja antes [Atualizando o Amplia da versão 4.x para 5.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-50).
Esta imagem requer:
* **Blob storage** compartilhado entre todos os containers que executam a imagem -- veja [Configuração de blob storage](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-blob-storage)
* **Licença do PKI SDK** (no formato Base64)
* **Licença do Web PKI** (formato Base64/binário) -- necessária apenas se os usuários forem emitir certificados em seus computadores (procedimento de emissão na web)
* **Entradas de DNS** criadas previamente para:
* Acesso ao painel de controle (veja [Domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain))
* Publicação de LCRs (veja [Domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains))
* **Connection string** para um [banco de dados SQL Server ou PostgreSQL criado previamente](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database)
## Configuração
O container desta imagem é configurado por variáveis de ambiente. Baixe o [**arquivo de ambiente de exemplo**](https://cdn.lacunasoftware.com/amplia/docker/amplia.env) para ter
um modelo a ser preenchido com as configurações da imagem.
Para preencher a configuração `General__EncryptionKey`, gere uma chave de 256 bits para cifrar os dados sensíveis armazenados no banco de dados:
```sh
docker run lacunasoftware/amplia:5.0 -- gen-enc-key
```
Para preencher a configuração `General__RootPasswordHash`, escolha uma senha forte para o acesso root ao painel de controle e calcule o hash dela:
```sh
docker run -i lacunasoftware/amplia:5.0 -- hash-root-pass
```
> **Observação**
>
> Se quiser habilitar o gerenciamento de usuários, deixe a configuração `General__RootPasswordHash` em branco e, em vez disso, siga os passos de [Configuração de OpenID Connect](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-oidc)
## Portas expostas
A imagem do Amplia escuta na **porta 80**.
Configure um proxy reverso ou um balanceador de carga escutando nas duas portas padrão, de HTTP (80) e de HTTPS (443), e redirecionando
o tráfego das duas portas para a porta 80 do Amplia. Além disso, o seu setup deve preencher os headers de requisição `X-Forwarded-Proto`, `X-Forwarded-For`
e `X-Forwarded-Port`. A configuração `Bindings__UseReverseProxy=True` indica ao Amplia que as informações desses headers são
confiáveis.
## Exemplo
Em um ambiente de produção, normalmente você usaria um orquestrador de Docker e um SQL Server dedicado (ou um banco de dados oferecido como IaaS), mas, para fins de teste,
é possível executar uma instância do Amplia com uma instância do SQL Server Express (que é gratuito) usando apenas o Docker.
Comece criando um volume para o servidor de banco de dados:
```sh
docker volume create amplia_sql
```
Em seguida, inicie-o com uma senha de sua escolha (substitua `SOME_PASS` abaixo):
```sh
docker run -v amplia_sql:/var/opt/mssql -p 1433:1433 -e "MSSQL_PID=Express" -e "SA_PASSWORD=SOME_PASS" -e "ACCEPT_EULA=Y" mcr.microsoft.com/mssql/server:2019-latest
```
Verifique se há erros no console. Isso pode levar alguns minutos.
Quando o SQL Server estiver em execução, abra outro terminal para iniciar a instância do Amplia.
Crie um volume para usar como blob storage:
```sh
docker volume create amplia_data
```
Em seguida, baixe o [arquivo de ambiente de exemplo](https://cdn.lacunasoftware.com/amplia/docker/amplia.env), salve-o com o nome *amplia.env*
e preencha-o.
Na configuração da connection string, use o valor abaixo, substituindo `HOST_IP` pelo endereço IP do host e `SOME_PASS` pela
senha que você escolheu para o SQL Server:
```sh
ConnectionStrings__DefaultConnection=Data Source=HOST_IP;Initial Catalog=Amplia;User ID=sa;Password=SOME_PASS
```
Na configuração do blob storage, mantenha os valores padrão, já que vamos montar o volume criado em `/var/app`:
```sh
BlobStorage__Type=FileSystem
BlobStorage__Path=/var/app
```
Agora, execute o container com o arquivo de configuração, montando o volume `amplia_data` em `/var/app` e expondo a aplicação (que escuta na porta 80) na porta 8080 do host:
```sh
docker run --env-file amplia.env -v amplia_data:/var/app -p 8080:80 lacunasoftware/amplia:5.0
```
> **Dica**
>
> Se receber uma credencial com privilégios suficientes, o Amplia tenta criar o banco de dados no servidor (que é o que vai acontecer neste caso)
Verifique se há erros de configuração no console. Se tudo estiver configurado corretamente, você terá uma instância do Amplia em execução em [localhost:8080](http://localhost:8080/)
## Veja também
* [Configuração de envio de SMS](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-sms)
* [Configuração de repositórios de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores)
* [Configuração do gerenciamento de usuários](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-oidc)
# [Amplia - Setup em Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure)
> **Observação**
>
> A documentação deste sistema encontra-se em construção. Pedimos desculpas pela inconveniência. Por favor contate-nos caso
> precise de alguma informação que ainda não está documentada.
## Veja também
* [Instalação do Amplia em Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/install)
* [Atualização do Amplia em Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot)
# [Instalação do Amplia em Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/install)
Para instalar uma instância [*on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) em um [Azure App Service](https://docs.microsoft.com/azure/app-service/overview),
siga os passos abaixo. Para outras plataformas, [clique aqui](https://docs.lacunasoftware.com/articles/amplia/on-premises#ambientes-suportados).
> **Dica**
>
> Antes de começar, leia a seção [Planejamento antes da instalação](https://docs.lacunasoftware.com/articles/amplia/on-premises#planning).
> **Dica**
>
> Para permitir o uso do recurso de certificados SSL gratuitos pelo *App Service Managed Certificates*,
> sugerimos não escolher "naked domains", ou seja, domínios sem porção de subdomínio, por exemplo ~~patorum.com~~.
> Prefira, por exemplo, `ac.patorum.com`.
## Preparação
As instruções a seguir assumem que você já tem instalado localmente o [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) e já possui
os seguintes recursos criados na sua conta do Azure:
* Um *SQL Server* (a criação do *SQL database* é coberta por este artigo)
* Um *App Service Plan* com sistema operacional **Linux** (a criação do App Service é coberta por este artigo)
* Zonas de DNS referentes ao [domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain)
e aos [domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains)
Durante a instalação, serão criados alguns recursos:
* Um *Container Registry*
* Um banco de dados (*SQL database*)
* Uma *storage account*
* Um App Service
* Um Azure Key Vault (opcional)
Sugerimos criar um **resource group** para agrupar os recursos criados. Entretanto, essa é uma medida com propósito meramente de organização. O que
realmente é importante é que **todos os recursos sejam criados na mesma região**. Isso é fundamental para o funcionamento adequado do sistema.
> **Observação**
>
> Os passos abaixo descrevem a criação mais básica de cada recurso. Dependendo da sua infraestrutura você pode querer tomar cuidados
> adicionais de segurança ou resiliência, como por exemplo restringir o acesso a uma rede privada.
## Criação de um *Container registry*
Siga os procedimentos abaixo para criar um *container registry* para armazenar localmente uma cópia das imagens de Docker do sistema:
1. Na opção **Container registries**, clique em **+ Create**
1. Na primeira aba (*Basics*)
* Preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
* Tome nota do *registry name* escolhido
* Em *SKU*, escolha **Basic**
1. Clique em **Review + create**, em seguida em **Create**
Uma vez concluída a criação do *container registry*, replique a imagem de Docker do sistema para ele (substitua `MY_ACR_NAME` pelo nome escolhido
no passo anterior):
```sh
az login
```
> **Observação**
>
> Caso você tenha acesso a mais de uma conta no Azure, pode ser necessário passar argumento `--tenant YOUR_TENANT`
```sh
az acr login --name MY_ACR_NAME
az acr import --name MY_ACR_NAME --source docker.io/lacunasoftware/amplia:5.0.0 --image amplia:5.0.0
```
## Criação do banco de dados
Siga os procedimentos abaixo para criar um banco de dados para o sistema (você precisa ter um *SQL Server* previamente criado):
1. Na opção **SQL databases**, clique em **+ Create**
1. Na primeira aba (*Basics*), preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
1. Clique na aba **Additional settings**
1. Em **Collation** preencha `Latin1_General_100_CI_AI`
1. Clique em **Review + create**
1. Clique em **Create**
> **Aviso**
>
> A *collation* do banco de dados **PRECISA SER** `Latin1_General_100_CI_AI`. Criar o banco de dados com uma *collation* diferente provavelmente fará com que a instalação falhe!
Uma vez concluída a criação do banco de dados, clique em **Go to resource**. Em seguida, obtenha a *connection string*:
1. Clique em **Connection strings**
1. Tome nota da connection string exibida na seção **ADO.NET (SQL authentication)**
> **Observação**
>
> Substitua na connection string copiada o trecho `{your_password}` pela senha do banco de dados
## Criação de uma *storage account*
Precisamos de uma *storage account* para armazenar arquivos (o sistema armazena arquivos fora do banco de dados para mantê-lo organizado) e também os
logs de sistema. Siga os passos abaixo para criar uma *storage account* (caso queira utilizar uma conta existente, pule essa parte):
1. Em **Storage accounts**, clique em **+ Create**
1. Na primeira aba (*Basics*), preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
* Em *Performance*, escolha **Standard**
* Em *Redundancy*, escolha a opção que preferir (sugerimos a opção **Zone-redundant storage (ZRS)** -- [clique aqui](https://azure.microsoft.com/documentation/articles/storage-redundancy/)
para detalhes sobre as diferentes opções de replicação)
1. Clique em **Review**
1. Clique em **Create**
Uma vez concluída a criação da *storage account*, clique em **Go to resource**. Em seguida, obtenha uma *connection string*:
1. Nas configurações da *storage account*, clique em **Access keys**
1. Na seção **key1**, tome nota do valor do campo **Connection string**
## Criação do App Service
Siga os procedimentos abaixo para criar um *App Service* (você precisa ter um *App Service Plan* previamente criado com sistema operacional **Linux**):
1. Em **App Services**, clique em **+ Create** em seguida em **+ Web App**
1. Na primeira aba (*Basics*), preencha os dados conforme a sua infraestrutura (seguindo a região escolhida para o sistema)
* Em *Publish*, escolha **Docker Container**
* Em *Operating System*, escolha **Linux**
* Em *Region*, escolha a região onde está o seu *app service plan*
* Em *Linux Plan* , escolha o seu *app service plan*
* Clique em **Next : Docker >**
1. Na aba seguinte (*Docker*), preencha:
* Em *Options*, deixe a opção **Single Container**
* Em *Image Source*, selecione **Azure Container Registry**
* Em *Registry*, selecione o *container registry* criado anteriormente
* Em *Image* e *Tag*, selecione a imagem/tag do sistema conforme o comando `az acr import` realizado anteriormente
* Deixe a opção *Startup Command* **em branco**
* Clique em **Review + create**
1. Clique em **Create**
Uma vez concluída a criação do App Service, clique em **Go to resource**. Em seguida, tome nota de seu domínio, por exemplo `meu-app-service.azurewebsites.net`.
Vá em **Configuration** do App Service e adicione as seguintes configurações:
* `ASPNETCORE_ENVIRONMENT`: `Azure`
* `Bindings__HttpsMode`: `Strict`
* `STANDBY`: `True`
Salve as configurações.
Ainda na configuração do App Service, vá na aba **General settings** e, na opção **HTTPS Only**, marque **Off** (a própria aplicação se encarregará de exigir acesso via HTTPS da maneira mais adequada).
Em seguida, vá em **Custom domains** e copie o **Custom Domain Verification ID** (esse valor será necessário mais à frente).
## Configuração de domínios
Crie os apontamentos necessários para o [domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain) e
os [domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains):
> **Observação**
>
> Dependendo das suas escolhas, o procedimento abaixo pode ter que ser feito apenas 1 vez (se você escolheu apenas um
> domínio de acesso e pretende usá-lo também como domínio de acesso ao painel de controle) ou até 3 vezes (se você escolheu
> dois domínios de acesso e pretende usar um terceiro domínio para acessar o painel de controle).
> **Observação**
>
> Nas instruções abaixo, utilizaremos como exemplo a criação do apontamento para o domínio `app.patorum.com`
1. Em **DNS zones**, clique na zona à qual o domínio pertence (ex: `patorum.com`)
1. Clique em **+ Record set**
1. Em *Name*, digite a porção mais à esquerda do domínio (ex: `app`)
1. Em *Type*, selecione **CNAME**
1. Em *Alias record set*, selecione **No**
1. Em *Alias*, preencha o domínio do App Service (ex: `meu-app-service.azurewebsites.net`)
1. Clique em **OK**
1. Clique novamente em **+ Record set**
1. Em *Name*, digite `asuid.` seguido da porção mais à esquerda do domínio (ex: `asuid.app`)
1. Em *Type*, selecione **TXT**
1. Em *Value*, cole o valor do **Custom Domain Verification ID** anteriormente copiado do App Service
1. Clique em **OK**
> **Observação**
>
> Caso você não utilize o Azure como servidor de DNS das zonas, realize o procedimento equivalente no seu servidor de DNS
Após criar os apontamentos para todos os domínios (de 1 a 3 domínios dependendo das suas escolhas),
adicione os domínios ao *App Service* (repita o procedimento abaixo para cada domínio):
1. De volta às configurações do App Service, clique em **Custom domains**
1. Clique em **+ Add custom domain**
1. Selecione **All other domain services**
1. Deixe selecionadas as opções **App Service Managed Certificate** e **SNI SSL**
1. Em *Domain*, preencha o domínio (ex: `app.patorum.com`)
1. Clique em **Validate**
1. Após a validação, clique em **Add**
## Configuração do Amplia
No menu lateral do App Service, na seção *Development Tools*, clique em **SSH**, em seguida em **Go →**. Você será levado a um terminal. Navegue
para a pasta `/app`:
```bash
cd /app
```
> **Dica**
>
> Embora o terminal pareça iniciar na pasta `/app`, o comando acima **é necessário** devido a um bug do Azure
Execute o comando abaixo para gerar a chave criptográfica utilizada para cifrar valores sensíveis no banco de dados:
```cmd
dotnet Lacuna.Amplia.Site.dll -- gen-enc-key
```
Tome nota do valor gerado.
Escolha uma senha forte para proteger o acesso de *root* ao painel de controle, e calcule o hash dessa senha com o comando abaixo:
```cmd
dotnet Lacuna.Amplia.Site.dll -- hash-root-pass
```
Novamente, tome nota do valor gerado.
Feche o terminal, voltando ao portal do Azure. No App Service, vá em **Configuration** e adicione as seguintes configurações:
* `General__AppDiscriminator`: `Amplia`
* `General__EncryptionKey`: chave criptográfica gerada acima
* `General__RootPasswordHash`: hash da senha de *root* calculado acima
* `General__SiteUrl`: URL pública do site, localizada no [domínio de acesso ao painel de controle](https://docs.lacunasoftware.com/articles/amplia/on-premises#dashboard-domain) (ex: `https://ca.patorum.com/`)
* `General__SiteName`: nome da sua instância do Amplia, ex: *Patorum CA*
* `Oidc__Enabled`: `False` (desabilita a [integração com OpenID Connect](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-oidc), por ora)
Adicione, também, as configurações descritas nas seções a seguir.
### Domínios de acesso
Configure os [domínios de acesso](https://docs.lacunasoftware.com/articles/amplia/on-premises#access-domains):
* `Amplia__AccessDomains`: domínios de acesso separados por vírgula, ex: `ca.patorum.com,ca.patorum.org`
### PKI Suite
Configurações do PKI Suite:
* `PkiSuite__SdkLicense`: sua licença para PKI SDK, no formato Base64 (**obrigatório**)
* `PkiSuite__WebLicense`: sua licença para o componente Web PKI no formato binário (Base64). Somente obrigatório se usuário vai emitir certificados em seus computadores (procedimento de emissão web)
### Blob Storage
Configuração da *storage account*:
* `BlobStorage__ConnectionString`: *connection string* da *storage account* criada anteriormente
* `BlobStorage__ContainerName` (opcional): nome do *container* a ser utilizado para armazenar arquivos. Caso omitido, um container denominado *amplia* é utilizado.
### Logging
Configuração de log:
* `Serilog__WriteTo__0__Args__connectionString`: *connection string* da *storage account* criada anteriormente
* `Serilog__WriteTo__0__Args__storageTableName` (opcional): nome da tabela a ser utilizada para armazenar os logs. Caso omitido, uma tabela denominada *AmpliaLog* é utilizada.
### Connection string
Na seção *Connection strings* (final da página de configurações), clique em **+ New connection string** e preencha:
* **Name**: `DefaultConnection`
* **Value**: valor da connection string obtido durante a criação do banco de dados
* **Type**: escolha **SQLAzure**
Salve as configurações feitas até o momento clicando em **Save**.
### Armazenamento de chaves
Conforme explicado na seção [Armazenamento de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises#key-storage) do planejamento da instalação, é preciso configurar pelo menos um repositório de chaves para
armazenar chaves. No Azure, recomendamos armazenar chaves no **Azure Key Vault**. Para tanto, siga as [instruções de configuração](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/azure) usando
como nome do repositório de chaves o valor `Azure`. Em seguida, adicione a configuração:
* `Amplia__DefaultKeyStore`: `Azure`
Outra opção é utilizar o [repositório de chaves em banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/database). Nesse caso, adicione as seguintes configurações:
* `Amplia__DatabaseKeyStoreEnabled`: `True`
* `Amplia__DefaultKeyStore`: `Database`
### Configurações opcionais
Recomendamos não alterar essas configurações por ora. Uma vez que a sua instância do Amplia esteja no ar, você
pode visitar cada um dos links abaixo para habilitar funcionalidades opcionais:
* [Redirecionamento para HTTPS](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-https-redirect)
* [Envio de emails](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-email)
* [Integração com OpenID Connect](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-oidc) (necessário para gerenciamento de usuários)
* [Envio de SMS](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-sms) (requerido pela página de emissão de certificados para usuários finais)
> **Observação**
>
> Nos links acima, sempre que for mencionado algo como "na seção **Sec**, atribua a configuração **Conf** ao valor ...", no Azure App Services você deve
> compor o nome da configuração com os nomes da seção e da configuração separados por `__` (**dois** *underscores*), ou seja, no exemplo acima: `Sec__Conf`
## Iniciando o App Service
Por fim, remova a configuração `STANDBY` do App Service e salve as configurações. Em seguida, acesse a URL do painel de controle (o primeiro acesso pode demorar alguns instantes).
Autentique-se com a senha de *root* escolhida durante a configuração. Você então terá acesso ao painel de controle, e a instalação estará concluída.
## Veja também
* [Atualização do Amplia em Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/update)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot)
# [Resolução de problemas do Amplia (Azure App Services)](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot)
Problemas comuns:
* *HTTP Error 500.30 - ANCM In-Process Start Failure* ao tentar acessar o site - veja [Diagnosticando problemas na inicialização](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot/startup-errors)
* *HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure* ao tentar acessar o site - veja [Diagnosticando problemas na inicialização](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot/startup-errors)
> **Observação**
>
> Se você está encontrando um problema não listado, tente [verificar os logs de sistema](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot/check-logs) ou entre em contato conosco.
## Veja também
* [Verificando os logs de sistema](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot/check-logs)
* [Diagnosticando problemas na inicialização](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot/startup-errors)
# [Verificando os logs de sistema (Azure App Services)](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot/check-logs)
Por padrão, os logs de sistema ficam localizados na tabela `AppLogs` da conta de **Azure Storage** criada para o sistema no momento
da instalação.
Para conferir qual conta e tabela estão sendo utilizadas, vá em **App Services** > (selecione o app service) > **Configuration** e verifique o valor
das configurações `Serilog:WriteTo:0:Args:connectionString` e `Serilog:WriteTo:0:Args:storageTableName`.
> **Observação**
>
> Alguns erros, geralmente os que ocorrem durante a inicialização do sistema, podem não ser registrados no arquivo de log do sistema.
> Para diagnosticar tais erros, veja [Diagnosticando problemas na inicialização](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot/startup-errors).
# [Diagnosticando problemas na inicialização (Azure App Services)](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot/startup-errors)
Ao iniciar sua [instância *on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) hospedada no [Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure), é possível que ocorra
um dos seguintes erros:
* *HTTP Error 500.30 - ANCM In-Process Start Failure*
* *HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure*
Em ambos casos, siga os passos abaixo para habilitar os logs de inicialização do sistema.
## Habilitando logs de inicialização
Acesse os arquivos do App Service por FTP, navegue até a pasta */site/wwwroot* e copie o arquivo *web.config* para o seu computador.
Em seguida, edite o arquivo alterando o elemento `aspNetCore`, setando o valor do atributo `stdoutLogEnabled` para `true`.
Por exemplo, suponha que o elemento `aspNetCore` está inicialmente conforme abaixo:
```xml
```
Após alterar o atributo `stdoutLogEnabled` para `true`, o elemento ficaria da seguinte maneira:
```xml
```
Além disso, confirme que o atributo `stdoutLogFile` está com o valor `.\logs\stdout`, conforme acima.
Copie o arquivo *web.config* de volta para a pasta */site/wwwroot* do App Service via FTP, substituindo o arquivo antigo.
Aguarde alguns segundos e tente acessar o site novamente, em seguida recarregue os arquivos do App Service no seu acesso por FTP. Deve aparecer uma nova pasta chamada
*logs*. Entre na nova pasta e inspecione os arquivos. Eles devem revelar a causa raiz do problema.
> **Aviso**
>
> Lembre-se de alterar o atributo `stdoutLogEnabled` de volta para `false` após concluir o diagnóstico complete, já que um grande volume de logs podem ser gerados,
> potencialmente afetando o desempenho do sistema.
## See also
* [Resolução de problemas do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot)
# [Atualização do Amplia em Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/update)
Replique a última imagem de Docker do sistema para seu *container registry* (substitua `MY_ACR_NAME` pelo nome do *container registry*
escolhido na [instalação](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/install)):
```sh
az login
```
> **Observação**
>
> Caso você tenha acesso a mais de uma conta no Azure, pode ser necessário passar argumento `--tenant YOUR_TENANT`
```sh
az acr login --name MY_ACR_NAME
az acr import --name MY_ACR_NAME --source docker.io/lacunasoftware/amplia:5.0.0 --image amplia:5.0.0
```
Em seguida, no App Service do sistema, vá em **Deployment Center** e, no campo **Tag**, selecione a nova versão e clique em **Save**.
## Veja também
* [Instalação do Amplia em Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/install)
* [Resolução de problemas](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/troubleshoot)
# [Amplia - Utilitário de linha de comando](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool)
As [instâncias on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) disponibilizam um utilitário de linha de comando que pode ser
usado para realizar determinadas tarefas administrativas.
## Executando um comando
Abra um terminal e navegue até o diretório de instalação, que depende da plataforma de instalação
(o diretório pode ser diferente se você escolheu um diretório personalizado durante a instalação):
* Windows Server: `cd C:\inetpub\Amplia`
* Linux: `cd /usr/share/amplia`
Em seguida, execute:
```sh
dotnet Lacuna.Amplia.Site.dll [settings] -- command [command-options]
```
Ou, no Docker, basta executar o seguinte em qualquer diretório:
```sh
docker run -i lacunasoftware/amplia:5.0 [settings] -- command [command-options]
```
Os argumentos `settings` são configurações adicionais que sobrescrevem as configurações dos arquivos de configuração e das variáveis de ambiente,
por exemplo `--Section1:Name1=value1 --Section2:Name2=value2`.
Os argumentos `command-options` dependem de cada comando; consulte a documentação específica do comando.
Comandos disponíveis:
* [gen-enc-key](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/gen-enc-key)
* [hash-root-pass](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/hash-root-pass)
* [update-db](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/update-db)
* [test-email](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/test-email)
## Veja também
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Comando do Amplia: **check-key-store-partitions-lock**](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/check-key-store-partitions-lock)
## Nome
`check-key-store-partitions-lock` - Verifica o status de bloqueio das partições dos repositórios de chaves
## Sintaxe
Para verificar o status de todas as partições nos repositórios de chaves correspondentes:
```sh
dotnet Lacuna.Amplia.Site.dll -- check-key-store-partitions-lock --scan --output-path /path/to/results/file.csv
```
Ou, no Docker:
```sh
docker run -v /path/on/host:/out lacunasoftware/amplia:5.0 -- check-key-store-partitions-lock --scan --output-path /out/results.csv
```
## Descrição
O comando `check-key-store-partitions-lock` verifica o status de bloqueio das partições em relação às partições físicas correspondentes no HSM.
## Veja também
* [Utilitário de linha de comando do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool)
# [Comando do Amplia: **gen-enc-key**](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/gen-enc-key)
## Nome
`gen-enc-key` - Gera uma chave de criptografia aleatória (para cifrar colunas sensíveis do banco de dados)
## Sintaxe
```sh
dotnet Lacuna.Amplia.Site.dll -- gen-enc-key
```
Ou, no Docker:
```sh
docker run lacunasoftware/amplia:5.0 -- gen-enc-key
```
## Descrição
O comando `gen-enc-key` gera uma chave de criptografia de 256 bits para ser usada na configuração `General__EncryptionKey`, que o Amplia usa para cifrar os dados sensíveis
armazenados no banco de dados.
## Veja também
* [Utilitário de linha de comando do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool)
# [Comando do Amplia: **hash-root-pass**](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/hash-root-pass)
## Nome
`hash-root-pass` - Calcula um hash com salt para uma senha root informada
## Sintaxe
```sh
dotnet Lacuna.Amplia.Site.dll -- hash-root-pass
```
Ou, no Docker:
```sh
docker run -i lacunasoftware/amplia:5.0 -- hash-root-pass
```
## Descrição
O comando `hash-root-pass` deve ser usado com o *modo de autenticação root*. Use-o para calcular um hash com salt da senha root desejada
e, em seguida, defina o hash na configuração `General__RootPasswordHash`.
A senha é lida do `stdin`.
## Veja também
* [Utilitário de linha de comando do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool)
* Modo de autenticação root
# [Comando do Amplia: **test-email**](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/test-email)
## Nome
`test-email` - Testa o envio de e-mails
## Sintaxe
```sh
dotnet Lacuna.Amplia.Site.dll [settings] -- test-email
```
Ou, no Docker:
```sh
docker run -i lacunasoftware/amplia:5.0 [settings] -- test-email
```
Os argumentos `settings` são configurações adicionais que sobrescrevem as configurações dos arquivos de configuração e das variáveis de ambiente,
por exemplo `--Section1:Name1=value1 --Section2:Name2=value2`.
## Descrição
O comando `test-email` testa a configuração de e-mail tentando enviar uma mensagem de teste para o endereço informado.
## Veja também
* [Utilitário de linha de comando do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool)
# [Comando do Amplia: **update-db**](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/update-db)
## Nome
`update-db` - Atualiza o modelo do banco de dados
## Sintaxe
```sh
dotnet Lacuna.Amplia.Site.dll [settings] -- update-db
```
Ou, no Docker:
```sh
docker run -i lacunasoftware/amplia:5.0 [settings] -- update-db
```
Os argumentos `settings` são configurações adicionais que sobrescrevem as configurações dos arquivos de configuração e das variáveis de ambiente,
por exemplo `--Section1:Name1=value1 --Section2:Name2=value2`.
## Descrição
O comando `update-db` atualiza o modelo do banco de dados aplicando uma série de *migrações*. Ele deve ser usado apenas quando
[o Amplia é executado sem privilégios db_owner](https://docs.lacunasoftware.com/articles/amplia/on-premises/unprivileged-db-user).
Como, nesse cenário, as credenciais de banco de dados do Amplia não concedem permissão para alterar o modelo do banco, normalmente
você vai executar este comando passando uma **connection string privilegiada**:
```sh
dotnet Lacuna.Amplia.Site.dll --ConnectionStrings:DefaultConnection="Data Source=SERVER;Initial Catalog=DATABASE;User ID=USERNAME;Password=PASSWORD" -- update-db
```
> **Dica**
>
> No Linux, se a sua senha contiver o caractere `!`, use aspas simples, por exemplo `--ConnectionStrings:DefaultConnection='...'`, para evitar
> expansões do histórico do shell
## Veja também
* [Utilitário de linha de comando do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool)
* [Executando o Amplia sem privilégios db_owner](https://docs.lacunasoftware.com/articles/amplia/on-premises/unprivileged-db-user)
# [Repositórios de chaves - Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores)
As chaves das suas Autoridades Certificadoras (ACs) podem ficar em vários locais, chamados *repositórios de chaves* (key stores). A escolha de
onde guardar as chaves das ACs é uma das decisões mais importantes no planejamento de uma AC ou de uma Infraestrutura de Chaves Públicas (ICP).
Alguns dos locais em que você pode guardar chaves são:
* O banco de dados
* Um *Hardware Security Module* (HSM)
* Um token criptográfico USB
* Um [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/)
Cada opção tem vantagens e desvantagens.
Chaves guardadas no **banco de dados** não têm custo e exigem o mínimo de configuração, mas é difícil controlar o acesso a elas. Embora as
chaves sejam armazenadas com criptografia de 256 bits, pode ser difícil restringir o acesso à "chave mestra" usada para criptografá-las.
Do ponto de vista de segurança e confiabilidade, talvez a melhor opção seja guardar as chaves em um **HSM**. Esses dispositivos permitem gerar
chaves dentro de um ambiente protegido criptograficamente, de modo que elas nunca saiam desse ambiente, mas ainda assim possam ter backup
(normalmente com dois HSMs). A desvantagem é o custo, que costuma ir de alguns a vários milhares de dólares por unidade, dependendo do
fabricante e dos recursos do dispositivo escolhido. Manter HSMs também pode ser caro, já que em geral exige pessoal especializado.
Um **token criptográfico USB** é bem mais barato, custa cerca de 10 dólares por unidade e também gera chaves dentro de um ambiente protegido
criptograficamente, do qual elas nunca saem. No entanto, as chaves geradas dentro de tokens criptográficos normalmente não podem ter backup, o
que deixa a AC vulnerável à perda das chaves, um evento grave para uma AC.
Uma opção que reúne o melhor dos repositórios de chaves em HSM com custo relativamente baixo é o **[Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/)**,
que pode ser visto como um HSM em nuvem. As chaves ficam em HSMs validados FIPS 140-2 nível 2, por um custo relativamente baixo, de cerca de 5
dólares por mês por chave. Esse serviço da Microsoft viabilizou o uso de HSMs em projetos que, de outra forma, não teriam orçamento para isso.
> **Dica**
>
> A instância do Amplia em nuvem da Lacuna Software (oferta SaaS) guarda todas as chaves de AC no Azure Key Vault, e recomendamos fortemente
> essa opção tanto para instâncias instaladas em nuvem quanto em servidores locais.
## Tipos de repositório de chaves
Independentemente de onde você decidir guardar as chaves, os repositórios de chaves normalmente oferecem suporte a um ou mais protocolos de
comunicação. O Amplia se comunica com os repositórios de chaves por esses protocolos, que definem os **tipos de repositório de chaves**
compatíveis com o Amplia.
Os tipos de repositório de chaves compatíveis são:
* [Banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/database)
* [PKCS #11](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/pkcs11)
* [Windows CryptoAPI (CAPI)](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/capi)
* [Windows Cryptography API - Next Generation (CNG)](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/cng)
* [Azure Key Vault](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/azure)
* [HSM Dinamo](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/dinamo)
* [HSM Kryptus](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/kryptus)
## Configuração dos repositórios de chaves
Na seção **KeyStores** do arquivo de configuração, cada subseção tem o nome de um repositório de chaves e contém a configuração dele. Por
exemplo:
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[KeyStores:Store1]
Type=...
Setting1=...
Setting2=...
[KeyStores:Store2]
Type=...
Setting1=...
Setting2=...
Setting3=...
```
Exemplo (variáveis de ambiente):
```sh
KeyStores__Store1__Type=...
KeyStores__Store1__Setting1=...
KeyStores__Store1__Setting2=...
KeyStores__Store2__Type=...
KeyStores__Store2__Setting1=...
KeyStores__Store2__Setting2=...
KeyStores__Store2__Setting3=...
```
Exemplo (arquivo de configuração *.json*):
```json
...
"KeyStores": {
"Store1": {
"Type": "...",
"Setting1": "...",
"Setting2": "..."
},
"Store2": {
"Type": "...",
"Setting1": "...",
"Setting2": "...",
"Setting3": "..."
},
}
...
```
A configuração **Type** de cada repositório de chaves define o tipo do repositório, e as demais configurações dependem do provider escolhido.
Veja o artigo de cada tipo de repositório de chaves para as instruções de configuração (links acima).
# [Armazenamento de chaves em Azure Key Vault](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/azure)
> **Observação**
>
> Os repositórios de chaves do Azure Key Vault são compatíveis com todas as plataformas suportadas.
O [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) pode ser considerado um HSM na nuvem. As chaves são armazenadas em HSMs com
validação FIPS 140-2 Nível 2, por um custo relativamente baixo, de cerca de 5 dólares por mês por chave. Esse serviço da Microsoft viabilizou o uso de HSMs
em projetos que, de outra forma, não teriam o orçamento necessário.
> **Dica**
>
> A instância do Amplia na nuvem da Lacuna Software (oferta SaaS) armazena todas as chaves de AC no Azure Key Vault, e recomendamos fortemente o seu uso
> tanto em instâncias instaladas na nuvem quanto em servidores locais.
## Procedimento
Para configurar um repositório de chaves do Azure Key Vault no Amplia, entre no [portal do Azure](https://portal.azure.com) e faça as configurações a seguir:
### Criando um aplicativo e um segredo de autenticação
Crie um aplicativo correspondente à sua instância do Amplia:
1. Na opção **Microsoft Entra ID**, acesse **App registrations** e clique em **New registration**
1. Informe um nome para o aplicativo
1. Mantenha os demais campos com as opções padrão
1. Clique em **Register**
Depois que o aplicativo for criado, anote o **Application (client) ID**.
Gere um segredo para identificação do aplicativo:
1. Nas configurações do aplicativo, clique em **Certificates & secrets**
1. Em **Client secrets**, clique em **New client secret**
1. Preencha uma descrição e escolha uma validade
1. Clique em **Add**
1. **Copie o valor exibido** na coluna **Value** (não será possível recuperar esse valor depois!)
> **Aviso**
>
> O client secret sempre expira (a validade máxima disponível é de 24 meses). Antes que ele expire, gere um novo e atualize a configuração
> **AppKey**; caso contrário, o Amplia deixará de conseguir acessar o Key Vault. Para evitar isso, você pode se autenticar com um
> certificado (veja a configuração **CertThumb** abaixo).
### Criando o Key Vault
Crie o Key Vault (pule esta parte se quiser usar um Key Vault existente):
1. Na opção **Key vaults**, clique em **Create**
1. Preencha os dados de acordo com a sua infraestrutura
1. Na opção *Pricing tier*, escolha **Premium** para poder gerar chaves armazenadas em HSM
1. Na aba **Access configuration**, mantenha o *Permission model* padrão, **Azure role-based access control**
1. Clique em **Review + create**
1. Clique em **Create**
Quando a criação do Key Vault terminar, clique em **Go to resource**. Em seguida, anote o **Vault URI** do Key Vault, exibido na página
*Overview* (ex.: `https://my-key-vault.vault.azure.net/`).
### Concedendo permissões ao aplicativo
A forma de conceder permissões ao aplicativo depende do *Permission model* do Key Vault, exibido em **Settings** > **Access configuration**.
Os Key Vaults novos usam **Azure role-based access control** por padrão. Key Vaults mais antigos podem usar **Vault access policy**.
Se o Key Vault usar **Azure role-based access control**:
1. No Key Vault, clique em **Access control (IAM)** e, depois, em **Add** > **Add role assignment**
1. Na aba **Role**, selecione **Key Vault Crypto Officer** e clique em **Next**
1. Na aba **Members**, escolha **User, group, or service principal** e clique em **Select members**
1. Selecione o aplicativo criado e clique em **Select**
1. Clique em **Review + assign** e, depois, em **Review + assign** novamente para confirmar
> **Observação**
>
> O role **Key Vault Crypto Officer** é necessário porque o Amplia cria chaves no Key Vault. O role **Key Vault Crypto User** não permite
> criar chaves.
Se o Key Vault usar **Vault access policy**:
1. Nas configurações do Key Vault, clique em **Access policies** e, depois, em **Create**
1. Na aba **Permissions**, sem usar um template, selecione apenas as seguintes *Key permissions*: **Get**, **List**, **Create**, **Verify** e **Sign**. Depois, clique em **Next**
1. Na aba **Principal**, selecione o aplicativo criado e clique em **Next**
1. Clique em **Next** até chegar à etapa de revisão e, depois, clique em **Create**
### Configuração do Amplia
Edite o seu arquivo de configuração JSON ou as configurações do seu App Service e adicione à seção **KeyStores** uma seção com um nome
que identifique o Key Vault (veja o exemplo abaixo), com as seguintes configurações:
* **Type**: `AzureKeyVault`
* **Endpoint**: Vault URI do Key Vault (exibido na página *Overview* do Key Vault) -- ex.: https://my-pki.vault.azure.net/
* **AppId**: ID do aplicativo usado para autenticação no Azure
* **AppKey**: chave secreta do aplicativo usada para autenticação no Azure (para mais segurança, omita este valor e informe o `CertThumb` em vez dele)
* **CertThumb**: thumbprint do certificado usado para autenticação no Azure (em hexadecimal, como fornecido pelo portal do Azure)
* **TenantId** (opcional, Amplia 5.0 ou mais recente): ID do tenant do Microsoft Entra no qual autenticar. Se omitido, o tenant é obtido do desafio de autenticação do Key Vault
* **UseManagedIdentity** (Amplia 5.0 ou mais recente): defina como `true` para autenticar com a identidade gerenciada do host onde o Amplia é executado (por exemplo, a identidade gerenciada do App Service dele)
em vez de uma chave secreta ou certificado do aplicativo. Nesse caso, deixe **AppKey** e **CertThumb** em branco e conceda as permissões descritas acima à identidade gerenciada, em vez de a um aplicativo
* **UseHsm**: por padrão, são criadas chaves [protegidas por HSM](https://docs.microsoft.com/en-us/azure/key-vault/key-vault-hsm-protected-keys). Para usar
chaves "de software", defina este valor como `false`
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[KeyStores:MyKeyVault]
Type=AzureKeyVault
Endpoint=https://xxx.vault.azure.net/
AppId=00000000-0000-0000-0000-000000000000
AppKey=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
```
Exemplo (variáveis de ambiente):
```sh
KeyStores__MyKeyVault__Type=AzureKeyVault
KeyStores__MyKeyVault__Endpoint=https://xxx.vault.azure.net/
KeyStores__MyKeyVault__AppId=00000000-0000-0000-0000-000000000000
KeyStores__MyKeyVault__AppKey=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
```
Exemplo (arquivo de configuração *.json*):
```json
"KeyStores": {
...,
"MyKeyVault": {
"Type": "AzureKeyVault",
"Endpoint": "https://xxx.vault.azure.net/",
"AppId": "00000000-0000-0000-0000-000000000000",
"AppKey": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
},
...
}
```
## Veja também
* [Repositórios de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores)
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Armazenamento de chaves via CAPI](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/capi)
> **Observação**
>
> Os repositórios de chaves CAPI só são compatíveis com instalações em Windows Server.
A **Windows CryptoAPI (CAPI)** trabalha com diversos **Cryptographic Service Providers (CSPs)**, que fazem de fato o trabalho
de fornecer vários "serviços criptográficos", como o armazenamento de chaves. Dispositivos como HSMs (Hardware Security Modules)
e tokens criptográficos USB normalmente fornecem um CSP CAPI que pode ser usado para se comunicar com o dispositivo.
Cada CSP CAPI é identificado pelo seu *nome* e pelo seu *tipo*. Se estiver usando um HSM ou um token criptográfico, consulte a
documentação do dispositivo para encontrar o nome e o tipo do CSP.
Para configurar um repositório de chaves CAPI no Amplia, use as seguintes configurações:
* **Type**: `CAPI`
* **ProviderName**: nome do CSP
* **ProviderType**: tipo do CSP. Os valores válidos são:
* **PROV_RSA_FULL**
* PROV_RSA_SIG
* PROV_DSS
* PROV_FORTEZZA
* PROV_MS_EXCHANGE
* PROV_SSL
* PROV_RSA_SCHANNEL
* PROV_DSS_DH
* PROV_EC_ECDSA_SIG
* PROV_EC_ECNRA_SIG
* PROV_EC_ECDSA_FULL
* PROV_EC_ECNRA_FULL
* PROV_DH_SCHANNEL
* PROV_SPYRUS_LYNKS
* PROV_RNG
* PROV_INTEL_SEC
* PROV_REPLACE_OWF
* **PROV_RSA_AES**
* **ExportableKeys**: por padrão, as chaves são geradas marcadas como exportáveis. Para gerar chaves não exportáveis, defina esta configuração como `false`.
* **UseMachineStore**: alguns CSPs têm o conceito de armazenar as chaves no *repositório do usuário* (user store) ou no *repositório da máquina* (machine store) — o principal exemplo é o CSP nativo do Windows.
Por padrão, é usado o repositório do usuário. Defina esta configuração como `true` para usar o repositório da máquina.
* **Pin**: o PIN do repositório, se necessário
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[KeyStores:MyCapiKeyStore]
Type=Capi
ProviderName=...
ProviderType=PROV_RSA_FULL
```
Exemplo (variáveis de ambiente):
```sh
KeyStores__MyCapiKeyStore__Type=Capi
KeyStores__MyCapiKeyStore__ProviderName=...
KeyStores__MyCapiKeyStore__ProviderType=PROV_RSA_FULL
```
Exemplo (arquivo de configuração *.json*):
```json
"KeyStores": {
...,
"MyCapiKeyStore": {
"Type": "Capi",
"ProviderName": "...",
"ProviderType": "PROV_RSA_FULL"
},
...
}
```
> **Dica**
>
> Se a documentação do HSM não deixar claro qual é o *tipo* do CSP, tente usar `PROV_RSA_FULL` ou `PROV_RSA_AES`.
## Repositórios de chaves CAPI comuns
Token criptográfico USB Safenet eToken:
```json
"CapiToken": {
"Type": "Capi",
"ProviderName": "eToken Base Cryptographic Provider",
"ProviderType": "PROV_RSA_FULL",
"Pin": "XXXX"
}
```
## Veja também
* [Repositórios de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores)
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Armazenamento de chaves via CNG](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/cng)
> **Observação**
>
> Os repositórios de chaves CNG só são compatíveis com instalações em Windows Server.
A **Cryptography API: Next Generation (CNG)** trabalha com o armazenamento de chaves por meio de diversos **Key Storage Providers (KSPs)**,
que fazem de fato o trabalho de armazenar as chaves. Dispositivos como HSMs (Hardware Security Modules) e tokens criptográficos USB podem
fornecer um KSP CNG que pode ser usado para se comunicar com o dispositivo.
Cada KSP CNG é identificado por um *nome*. Se estiver usando um HSM ou um token criptográfico, consulte a documentação do dispositivo
para encontrar o nome do KSP. Veja também, na seção abaixo, os nomes de KSPs comuns.
Para configurar um repositório de chaves CNG no Amplia, use as seguintes configurações:
* **Type**: `CNG`
* **ProviderName**: nome que identifica o KSP a ser usado
* **UseMachineStore**: alguns KSPs têm o conceito de armazenar as chaves no *repositório do usuário* (user store) ou no *repositório da máquina* (machine store) — o principal exemplo é o KSP nativo do sistema operacional.
Por padrão, é usado o repositório do usuário. Defina esta configuração como `true` para usar o repositório da máquina.
* **Pin**: o PIN do repositório, se necessário
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[KeyStores:MyCngKeyStore]
Type=Cng
ProviderName=...
```
Exemplo (variáveis de ambiente):
```sh
KeyStores__MyCngKeyStore__Type=Cng
KeyStores__MyCngKeyStore__ProviderName=...
```
Exemplo (arquivo de configuração *.json*):
```json
"KeyStores": {
...,
"MyCngKeyStore": {
"Type": "Cng",
"ProviderName": "..."
},
...
}
```
## Repositórios de chaves CNG comuns
Token criptográfico USB Safenet eToken:
```json
"CngToken": {
"Type": "Cng",
"ProviderName": "SafeNet Smart Card Key Storage Provider"
}
```
## Veja também
* [Repositórios de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores)
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Armazenamento de chaves em banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/database)
> **Observação**
>
> A documentação deste sistema está em construção. Pedimos desculpas por qualquer inconveniente que isso possa causar. Entre em contato conosco
> se precisar de alguma informação que ainda não esteja documentada.
## Configurando o Amplia para armazenar chaves no banco de dados
Ao contrário dos outros repositórios de chaves, não é preciso adicionar uma entrada na seção de configuração **KeyStores** para usar o
repositório de chaves no banco de dados. Em vez disso, ele pode ser habilitado na seção **Amplia** do arquivo de configuração:
* `DatabaseKeyStoreEnabled`: defina esta configuração como `true` para habilitar o repositório de chaves no banco de dados, chamado *Database*
Você pode usar o nome do repositório (*Database*) para configurar o `DefaultKeyStore`.
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[Amplia]
DatabaseKeyStoreEnabled=True
DefaultKeyStore=Database
```
Exemplo (variáveis de ambiente):
```sh
Amplia__DatabaseKeyStoreEnabled=True
Amplia__DefaultKeyStore=Database
```
Exemplo (arquivo de configuração *.json*):
```json
...,
"Amplia": {
...,
"DatabaseKeyStoreEnabled": true,
"DefaultKeyStore": "Database",
...
},
...
```
## Veja também
* [Repositórios de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores)
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Armazenamento de chaves em HSM Dinamo](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/dinamo)
> **Observação**
>
> Os repositórios de chaves Dinamo são compatíveis com todas as plataformas suportadas.
Para armazenar chaves em um [HSM Dinamo](https://www.dinamonetworks.com/en/hardware-security-module-hsm/), use as seguintes configurações:
* **Type**: `Dinamo`
* **Address**: endereço IP ou nome do host do HSM
* **User** e **Password**: credenciais de um usuário Operador ou de um usuário comum (veja abaixo)
Se você pretende usar o HSM para armazenar chaves de AC, as credenciais podem ser de um usuário Operador ou de um usuário comum.
Se você pretende usar o HSM para armazenar chaves protegidas por PIN, normalmente em conjunto com o [módulo Lacuna PSC](https://docs.lacunasoftware.com/articles/psc), as credenciais
devem ser de um usuário **Operador**.
Exemplo de configuração (arquivo *.ini* ou *.conf*):
```ini
[KeyStores:MyDinamoHsm]
Type=Dinamo
Address=10.1.2.3
User=SOME_USER
Password=SOME_PASSWORD
```
Exemplo de configuração (variáveis de ambiente):
```bash
KeyStores__MyDinamoHsm__Type=Dinamo
KeyStores__MyDinamoHsm__Address=10.1.2.3
KeyStores__MyDinamoHsm__User=SOME_USER
KeyStores__MyDinamoHsm__Password=SOME_PASSWORD
```
Exemplo de configuração (arquivo *.json*):
```json
"KeyStores": {
"MyDinamoHsm": {
"Type": "Dinamo",
"Address": "10.1.2.3",
"User": "SOME_USER",
"Password": "SOME_PASSWORD"
}
}
```
Se você pretende usar o HSM em conjunto com o módulo Lacuna PSC, também é preciso definir a variável de ambiente `HSM_DISABLE_SESSION_CACHE` como `1`. Isso não
pode ser feito no arquivo de configuração do Amplia: *precisa* ser uma variável de ambiente. No Linux, isso significa adicionar o seguinte ao arquivo
`/etc/systemd/system/amplia.service`:
```ini
[Service]
...
Environment=HSM_DISABLE_SESSION_CACHE=1
```
Se o HSM estiver no modo de operação **RM1** ou **RM2**, também é preciso definir a variável de ambiente `HSM_DISABLE_LEGACY_OPERATIONS` como `1`. Isso não
pode ser feito no arquivo de configuração do Amplia: *precisa* ser uma variável de ambiente. No Linux, isso significa adicionar o seguinte ao arquivo
`/etc/systemd/system/amplia.service`:
```ini
[Service]
...
Environment=HSM_DISABLE_LEGACY_OPERATIONS=1
```
## Veja também
* [Amplia - Repositórios de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores)
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Armazenamento de chaves em HSM Kryptus](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/kryptus)
> **Observação**
>
> Os repositórios de chaves Kryptus são compatíveis com todas as plataformas suportadas.
Para armazenar chaves em um [HSM Kryptus kNET](https://kryptus.com/en/network-hsm-knet/), use as seguintes configurações:
* **Type**: `Kryptus`
* **Host**: endereço IP ou nome do host do VHSM
* **Port**: porta **HTTPS** do VHSM (*não* a porta TTLV)
Se você pretende usar o HSM para armazenar chaves de AC, informe as credenciais de um **usuário comum** (*não* um usuário VCO) nas seguintes configurações:
* **RootUsername**
* **RootPassword**
Se você pretende usar o HSM para armazenar chaves de usuário protegidas por PIN (normalmente em conjunto com o [módulo Lacuna PSC](https://docs.lacunasoftware.com/articles/psc)),
informe as credenciais de um **usuário VCO** (*não* um usuário comum) nas seguintes configurações:
* **VcoUsername**
* **VcoPassword**
> **Observação**
>
> Se você pretende usar o mesmo VHSM para os dois casos de uso, defina as 4 configurações. No entanto, isso seria muito incomum, já que é
> recomendado manter as chaves de AC e as chaves de usuário em VHSMs separados.
Exemplo de configuração (arquivo *.ini* ou *.conf*):
```bash
[KeyStores:MyKryptusHsm]
Type=Kryptus
Host=10.1.2.3
Port=50000
RootUsername=SOME_REGULAR_USER
RootPassword=SOME_PASSWORD
VcoUsername=SOME_VCO_USER
VcoPassword=SOME_PASSWORD
```
Exemplo de configuração (variáveis de ambiente):
```bash
KeyStores__MyKryptusHsm__Type=Kryptus
KeyStores__MyKryptusHsm__Host=10.1.2.3
KeyStores__MyKryptusHsm__Port=50000
KeyStores__MyKryptusHsm__RootUsername=SOME_REGULAR_USER
KeyStores__MyKryptusHsm__RootPassword=SOME_PASSWORD
KeyStores__MyKryptusHsm__VcoUsername=SOME_VCO_USER
KeyStores__MyKryptusHsm__VcoPassword=SOME_PASSWORD
```
Exemplo de configuração (arquivo *.json*):
```json
"KeyStores": {
"MyKryptusHsm": {
"Type": "Kryptus",
"Host": "10.1.2.3",
"Port": 50000,
"RootUsername": "SOME_REGULAR_USER",
"RootPassword": "SOME_PASSWORD",
"VcoUsername": "SOME_VCO_USER",
"VcoPassword": "SOME_PASSWORD"
}
}
```
> **Observação**
>
> Observe que o valor de `Port` mostrado acima é apenas um exemplo. Não há valor padrão para esta configuração: ele depende inteiramente da configuração do seu VHSM.
## Solução de problemas
Você pode encontrar o seguinte erro ao usar um repositório de chaves Kryptus no Ubuntu 20.04:
```text
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
---> Interop+OpenSsl+SslException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL.
---> Interop+Crypto+OpenSslCryptographicException: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
--- End of inner exception stack trace ---
at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, ReadOnlySpan`1 input, Byte[]& sendBuf, Int32& sendCount)
at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteSslContext& context, ReadOnlySpan`1 inputBuffer, Byte[]& outputBptions sslAuthenticationOptions)
--- End of inner exception stack trace ---
at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream streamllationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream streamllationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
...
```
Nesse caso, edite o arquivo de configuração do OpenSSL:
```sh
nano /etc/ssl/openssl.cnf
```
Adicione o seguinte no início do arquivo:
```ini
#
# This is part 1/2 of a workaround for SSL handshake issues with Kryptus HSMs, for more info
# see https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/kryptus
#
openssl_conf = default_conf
```
Em seguida, adicione o seguinte no final do arquivo:
```ini
#
# This is part 2/2 of a workaround for SSL handshake issues with Kryptus HSMs, for more info
# see https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/kryptus
#
[default_conf]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT:@SECLEVEL=1
```
Para mais informações, consulte a [issue relacionada no repositório do .NET runtime](https://github.com/dotnet/runtime/issues/46271#issuecomment-749118036).
## Veja também
* [Repositórios de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores)
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Armazenamento de chaves via PKCS #11](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/pkcs11)
> **Observação**
>
> Os repositórios de chaves PKCS #11 só são compatíveis com instalações em Windows Server e Linux. Para armazenar chaves em um HSM numa instância do Amplia hospedada no Azure App Services, use um [repositório de chaves Azure Key Vault](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/azure).
Dispositivos como HSMs (Hardware Security Modules) e tokens criptográficos USB normalmente oferecem suporte à comunicação pelo
protocolo PKCS #11.
Para configurar um repositório de chaves PKCS #11 no Amplia, use as seguintes configurações:
* **Type**: `Pkcs11`
* **Module**: nome da biblioteca PKCS #11 (ex.: `eTPKCS11.dll`)
* **Pin**: PIN do token, se necessário
* **TokenSerialNumber** / **TokenLabel** (a partir da 4.8.0): se houver vários tokens, você pode especificar o token a ser usado com qualquer uma das duas configurações
Exemplo de configuração (arquivo *.ini* ou *.conf*):
```bash
[KeyStores:MyDevice]
Type=Pkcs11
Module=...
Pin=...
```
Exemplo de configuração (variáveis de ambiente):
```bash
KeyStores__MyDevice__Type=Pkcs11
KeyStores__MyDevice__Module=...
KeyStores__MyDevice__Pin=...
```
Exemplo de configuração (arquivo *.json*):
```json
"KeyStores": {
...,
"MyDevice": {
"Type": "Pkcs11",
"Module": "...",
"Pin": "..."
},
...
}
```
## Repositórios de chaves PKCS #11 comuns
Token criptográfico USB Safenet eToken (apenas um token conectado):
```ini
[KeyStores:eToken]
Type=Pkcs11
Module=eTPKCS11.dll
Pin=XXXX
```
```sh
KeyStores__eToken__Type=Pkcs11
KeyStores__eToken__Module=eTPKCS11.dll
KeyStores__eToken__Pin=XXXX
```
```json
"eToken": {
"Type": "Pkcs11",
"Module": "eTPKCS11.dll",
"Pin": "XXXX"
}
```
Token criptográfico USB Safenet eToken (vários tokens presentes, especificando o token a ser usado):
```ini
[KeyStores:eTokenA]
Type=Pkcs11
Module=eTPKCS11.dll
Pin=XXXX
TokenSerialNumber=01f5cfe4
```
```sh
KeyStores__eTokenA__Type=Pkcs11
KeyStores__eTokenA__Module=eTPKCS11.dll
KeyStores__eTokenA__Pin=XXXX
KeyStores__eTokenA__TokenSerialNumber=01f5cfe4
```
```json
"eTokenA": {
"Type": "Pkcs11",
"Module": "eTPKCS11.dll",
"Pin": "XXXX",
"TokenSerialNumber": "01f5cfe4"
}
```
## Usando um HSM Kryptus via PKCS #11
> **Dica**
>
> Para armazenar chaves em um HSM Kryptus, é recomendado usar o repositório de chaves [Kryptus](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/kryptus). Se, por algum motivo, você preferir
> se comunicar com o HSM via PKCS #11, continue lendo.
Você vai precisar dos seguintes parâmetros de um **VHSM** (HSM virtual) e de um **usuário comum** (*não* um usuário VCO) com autenticação por senha:
* **Endereço IP** do VHSM
* **Porta HTTPS** do VHSM (*não* a porta TTLV)
* **Nome de usuário** e **senha** do usuário comum
Primeiro, obtenha com a Kryptus ou com a Lacuna a versão mais recente do arquivo *kNET-PKCS11-Linux-X.Y.Z.tar*. Em seguida, execute:
```sh
sudo su -
tar xf kNET-PKCS11-Linux-X.Y.Z.tar
cd kNET-PKCS11-Linux-X.Y.Z
./install.sh
mkdir -p /var/amplia/.config/kryptus/knet/pkcs11
touch /var/amplia/.config/kryptus/knet/pkcs11/config.json
chown -R root:amplia /var/amplia/.config
chmod -R a=,u+rwX,g+rX /var/amplia/.config
nano /var/amplia/.config/kryptus/knet/pkcs11/config.json
```
Cole o texto a seguir, substituindo os valores pelos seus:
```json
{
"connections": {
"hsm_1": {
"ip": "IP_ADDRESS_OF_YOUR_VHSM",
"port": "HTTPS_PORT_OF_YOUR_VHSM"
}
},
"users": {
"SOME_USERNAME": {
"authentication_type": "password",
"connection": "hsm_1"
}
},
"log_verbosity": "full"
}
```
Segundo a Kryptus, a entrada **log_verbosity** pode assumir um dos seguintes valores:
* `none`: nada é registrado no log
* `key_only`: apenas as operações com chaves e os erros são registrados no log
* `quiet`: *key_only* + operações que usam o HSM, erros e avisos
* `info`: *quiet* + operações PKCS #11 (observação: ignora a função `C_GetTokenInfo`)
* `verbose`: *info* + argumentos resumidos das operações PKCS #11
* `full`: registra tudo no log, com o máximo de detalhes possível
> **Observação**
>
> Se nenhum nível de detalhamento for definido, ou seja, se o arquivo de configuração não tiver a entrada **log_verbosity**, o padrão será `quiet`.
Em seguida, no arquivo de configuração do Amplia:
```ini
[KeyStores:MyKryptusHsm]
Type=Pkcs11
Module=libkNETPKCS11.so
Pin=SOME_PASSWORD
```
```sh
KeyStores__MyKryptusHsm__Type=Pkcs11
KeyStores__MyKryptusHsm__Module=libkNETPKCS11.so
KeyStores__MyKryptusHsm__Pin=SOME_PASSWORD
```
```json
"MyKryptusHsm": {
"Type": "Pkcs11",
"Module": "libkNETPKCS11.so",
"Pin": "SOME_PASSWORD"
}
```
Para ter vários usuários no HSM, cada um mapeado para um repositório de chaves diferente no Amplia, adicione os usuários ao arquivo *config.json* e preencha a configuração `TokenLabel`
de cada repositório de chaves no Amplia com o nome de usuário correspondente.
### Alta disponibilidade
Para habilitar a alta disponibilidade (HA), é necessário configurar uma lista de endereços IP e de portas no arquivo de configuração. O exemplo abaixo mostra a configuração necessária:
```json
{
"connections": {
"hsm_1": {
"ip": ["IP_ADDRESS_A", "IP_ADDRESS_B"],
"port": ["HTTPS_PORT_ON_A", "HTTPS_PORT_ON_B"]
}
},
...
}
```
## Veja também
* [Repositórios de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores)
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Verificando a versão instalada do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/check-version)
Para verificar a versão que está instalada da sua instância [*on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia),
acesse o seguinte endereço a partir de um navegador:
```text
https://endereco-do-seu-amplia/api/system/info
```
Exemplo: https://amplia.lacunasoftware.com/api/system/info
O retorno é um JSON contendo a versão do sistema, por exemplo:
```json
{"productName":"Lacuna Amplia","productVersion":"5.x.x"}
```
## Veja também
* [Atualização do Amplia em Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/update)
* [Atualização do Amplia em Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/update)
* [Atualização do Amplia em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/update)
# [Configuração de blob storage - Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-blob-storage)
A seção de configuração **BlobStorage** define como a aplicação armazena e recupera arquivos.
## Sistema de arquivos
Armazena os arquivos no sistema de arquivos. Configurações disponíveis:
* **Path**: o caminho em que os arquivos serão armazenados.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=FileSystem
BlobStorage__Path=/var/files/myapp
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=FileSystem
Path=/var/files/myapp
```
Exemplo (configuração JSON):
```json
{
...,
"BlobStorage": {
"Type": "FileSystem",
"Path": "/var/files/myapp"
},
...
}
```
## Azure Storage Account
Armazena os arquivos em um container de uma conta de armazenamento do Azure (Azure Storage Account). Configurações disponíveis:
* **ConnectionString**: a connection string da conta de armazenamento.
* **ContainerName**: o nome do container em que os arquivos serão armazenados.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=Azure
BlobStorage__ConnectionString=DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net
BlobStorage__ContainerName=myapp-container
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=Azure
ConnectionString="DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net"
ContainerName=myapp-container
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "Azure",
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net",
"ContainerName": "myapp-container"
},
...
}
```
## AWS S3 (Simple Storage Service)
Armazena os arquivos em um bucket do S3. Configurações disponíveis:
* **Region**: a região do bucket.
* **BucketName**: o nome do bucket em que os arquivos serão armazenados.
* **AccessKey**: o ID da chave de acesso de um usuário do IAM que tenha acesso ao bucket.
* **SecretKey**: a chave de acesso secreta de um usuário do IAM que tenha acesso ao bucket.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=AwsS3
BlobStorage__Region=us-east-1
BlobStorage__BucketName=myappbucket
BlobStorage__AccessKey=MYACCESSKEYID
BlobStorage__SecretKey=MYSECRETACCESSKEY
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=AwsS3
Region=us-east-1
BucketName=myappbucket
AccessKey=MYACCESSKEYID
SecretKey=MYSECRETACCESSKEY
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "AwsS3",
"Region": "us-east-1",
"BucketName": "myappbucket",
"AccessKey": "MYACCESSKEYID",
"SecretKey": "MYSECRETACCESSKEY"
},
...
}
```
### Armazenamento de objetos compatível com S3
Também é possível usar um serviço auto-hospedado de armazenamento de objetos compatível com S3, conhecido como "clone do S3", como
o [minIO](https://min.io/). Nesse caso, em vez de *Region*, especifique **Endpoint**.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=AwsS3
BlobStorage__Endpoint=http://localhost:9000
BlobStorage__BucketName=myappbucket
BlobStorage__AccessKey=MYACCESSKEYID
BlobStorage__SecretKey=MYSECRETACCESSKEY
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=AwsS3
Endpoint=http://localhost:9000
BucketName=myappbucket
AccessKey=MYACCESSKEYID
SecretKey=MYSECRETACCESSKEY
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "AwsS3",
"Endpoint": "http://localhost:9000",
"BucketName": "myappbucket",
"AccessKey": "MYACCESSKEYID",
"SecretKey": "MYSECRETACCESSKEY"
},
...
}
```
Por padrão, quando um armazenamento compatível com S3 é configurado, é usado o endereçamento no estilo de caminho (path-style), em vez
do endereçamento no estilo de host virtual (virtual-hosted–style), que é o padrão quando se configura um bucket comum do AWS S3. Isso
pode ser alterado com a configuração **ForcePathStyle** (`true` ou `false`). Para mais informações sobre o endereçamento no S3, veja
[Virtual hosting of buckets](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html).
## Google Cloud Storage
Armazena os arquivos em um bucket do Google Cloud Storage. Você precisará de uma conta de serviço com o role Storage Object Admin.
Configurações disponíveis:
* **BucketName**: o nome do bucket em que os arquivos serão armazenados.
* **ProjectId**: o ID do projeto no Google Cloud.
* **PrivateKeyId**: o ID da chave privada da conta de serviço.
* **PrivateKey**: a chave privada da conta de serviço.
* **ClientEmail**: o e-mail da conta de serviço.
Exemplo (variáveis de ambiente):
```bash
BlobStorage__Type=Gcp
BlobStorage__BucketName=myappbucket
BlobStorage__ProjectId=my-app-project-id
BlobStorage__PrivateKeyId=MYPRIVATEKEYID
BlobStorage__PrivateKey=MYPRIVATEKEY
BlobStorage__ClientEmail=myserviceaccountemail@my-app-project-id.iam.gserviceaccount.com
```
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[BlobStorage]
Type=Gcp
BucketName=myappbucket
ProjectId=my-app-project-id
PrivateKeyId=MYPRIVATEKEYID
PrivateKey=MYPRIVATEKEY
ClientEmail=myserviceaccountemail@my-app-project-id.iam.gserviceaccount.com
```
Exemplo (configuração JSON):
```json
{
...
"BlobStorage": {
"Type": "Gcp",
"BucketName": "myappbucket",
"ProjectId": "my-app-project-id",
"PrivateKeyId": "MYPRIVATEKEYID",
"PrivateKey": "MYPRIVATEKEY",
"ClientEmail": "myserviceaccountemail@my-app-project-id.iam.gserviceaccount.com"
},
...
}
```
### Como obter as configurações do Google Cloud Storage
Primeiro, você precisa de um bucket do Google Cloud Storage para armazenar os arquivos. Para criar um bucket, use o botão **Create** no
console do Google Cloud Storage:

Escolha um nome para o novo bucket e clique em **Continue**:

Escolha a região do novo bucket:

Certifique-se de aplicar a prevenção do acesso público e o controle de acesso uniforme ao novo bucket:

Escolha a política de retenção de objetos e crie o bucket:

Pode aparecer um diálogo pedindo que você confirme que o acesso público será impedido. Clique em **Confirm** para continuar:

Em seguida, crie uma conta de serviço do Google Cloud para acessar o bucket. Para isso, use o botão **Create service account** na
página *Service Accounts*, em *IAM & Admin*:

A nova conta de serviço também precisa de um nome. Escolha um e clique em **Create and continue**:

Certifique-se de que a nova conta de serviço receba o role Storage Object Admin:

Se necessário, conceda acesso a entidades de segurança (principals) e clique em **Done** para concluir a criação da conta de serviço:

Com a conta de serviço criada, é preciso uma chave para autenticá-la. Clique na nova conta de serviço para abrir a página de detalhes
dela:

Nos detalhes da conta de serviço, vá para a seção **Keys**:

Na opção **Add key**, escolha **Create new key**:

Escolha o formato JSON e clique no botão **Create**:

Será baixado um arquivo JSON com a chave privada e outras informações da conta de serviço:

Nesse arquivo JSON, você encontra os campos `project_id`, `private_key_id`, `private_key` e `client_email`, usados para configurar o
serviço de Blob Storage.
> **Info**
>
> As informações deste arquivo JSON permitem acessar os recursos do GCP e não podem ser comprometidas! Guarde ou descarte o arquivo de
> forma segura.
# [Configuração de template de certificado de AC - Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-ca-cert-templates)
Para personalizar o perfil dos certificados de AC emitidos pela sua instância [on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia), configure a seção
`CACertificateTemplates` do arquivo de configuração. Dentro dela, adicione uma subseção chamada `RootCA` ou `IntermediateCA`, conforme você queira
personalizar as ACs raiz ou as intermediárias. Em cada subseção, as seguintes configurações são reconhecidas:
* **IncludeSubjectKeyId**: se a extensão *SubjectKeyIdentifier* deve ser incluída — `True` (padrão) ou `False`
* **IncludeAuthorityKeyId**: se a extensão *AuthorityKeyIdentifier* deve ser incluída — `True` (padrão) ou `False`
* **KeyUsages**: lista separada por vírgulas dos seguintes usos de chave (*key usages*) — o padrão é `KeyCertSign,CrlSign`
* `CrlSign`
* `DataEncipherment`
* `DecipherOnly`
* `DigitalSignature`
* `EncipherOnly`
* `KeyAgreement`
* `KeyCertSign`
* `KeyEncipherment`
* `NonRepudiation`
* **ExtendedKeyUsages**: lista separada por vírgulas dos seguintes usos estendidos de chave (*extended key usages*) — por padrão, nenhum
* `ClientAuth`
* `ServerAuth`
* `CodeSigning`
* `EmailProtection`
* `TimeStamping`
* `OcspSigning`
* `IpsecEndSystem`
* `IpsecTunnel`
* `IpsecUser`
* `Any`
* **RandomSerialNumber**: se deve ser usado um número de série aleatório — `True` ou `False` (padrão)
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[CACertificateTemplates:IntermediateCA]
KeyUsages=KeyCertSign,CrlSign,DigitalSignature
RandomSerialNumber=True
```
Exemplo (variáveis de ambiente):
```bash
CACertificateTemplates__IntermediateCA__KeyUsages=KeyCertSign,CrlSign,DigitalSignature
CACertificateTemplates__IntermediateCA__RandomSerialNumber=True
```
## Políticas de certificado
Em cada subseção (`RootCA` ou `IntermediateCA`), pode haver várias subseções chamadas `Policies:0`, `Policies:1` e assim por diante, com as seguintes
configurações:
* **Oid**: OID da política
* **StatementUrl**: URL da declaração da política
Em cada seção de política, pode haver uma subseção chamada `UserNotices` com vários valores `0`, `1` e assim por diante, cada um com um aviso ao usuário (*user notice*).
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[CACertificateTemplates:IntermediateCA:Policies:0]
Oid=1.2.3.4
StatementUrl=https://ca.patorum.com/ca-certificate-policy.pdf
[CACertificateTemplates:IntermediateCA:Policies:0:UserNotices]
0=This CA certificate is for test purposes
1=No end-user certificates should be accepted under this CA
```
Exemplo (variáveis de ambiente):
```bash
CACertificateTemplates__IntermediateCA__Policies__0__Oid=1.2.3.4
CACertificateTemplates__IntermediateCA__Policies__0__StatementUrl=https://ca.patorum.com/ca-certificate-policy.pdf
CACertificateTemplates__IntermediateCA__Policies__0__UserNotices__0=This CA certificate is for test purposes
CACertificateTemplates__IntermediateCA__Policies__0__UserNotices__1=No end-user certificates should be accepted under this CA
```
## Configuração legada
A seção de configuração **CACertificateTemplates** descrita acima foi introduzida na versão [4.22.0](https://docs.lacunasoftware.com/articles/amplia/changelog#v4-22-0) do Amplia. A
seção de configuração antiga, **CACertificateTemplate** (no singular), continua sendo aceita e afeta tanto os certificados de AC raiz quanto os de AC intermediária.
A sintaxe é um pouco diferente da descrita acima, principalmente nas configurações **KeyUsages** e **ExtendedKeyUsages**. Veja os exemplos abaixo.
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[CACertificateTemplate]
RandomSerialNumber=True
[CACertificateTemplate:KeyUsages]
0=KeyCertSign
1=CrlSign
2=DigitalSignature
```
Exemplo (variáveis de ambiente):
```bash
CACertificateTemplate__RandomSerialNumber=True
CACertificateTemplate__KeyUsages__0=KeyCertSign
CACertificateTemplate__KeyUsages__1=CrlSign
CACertificateTemplate__KeyUsages__2=DigitalSignature
```
# [Configuração de gerenciamento de certificados em nuvem - Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-cert-management)
O [Amplia](https://docs.lacunasoftware.com/articles/amplia) pode ser usado para hospedar certificados de usuário final com as respectivas chaves privadas, comumente chamados de certificados *em nuvem*.
Em geral, isso é feito em conjunto com o [Lacuna PSC](https://docs.lacunasoftware.com/articles/psc).
Esse recurso é configurado na seção **CertificateManagement**, que tem as seguintes configurações:
* **KeyStore**: nome do [repositório de chaves](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores) que será usado para gerar e armazenar as chaves dos certificados
* **IssuingService**: veja [serviço de emissão](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-cert-management#serviço-de-emissão) abaixo
* **PukGenerationLength**: tamanho dos PUKs gerados automaticamente. Padrão: `8`
* **IssueSessionTimeoutMinutes**: duração máxima, em minutos, de uma sessão de emissão. Aceita valores decimais. Padrão: `15`
* **AuthenticationSessionTimeoutMinutes**: duração máxima, em minutos, de uma sessão de autenticação. Aceita valores decimais. Padrão: `15`
* **OperationSessionTimeoutMinutes**: duração máxima, em minutos, de uma sessão de operação. Aceita valores decimais. Padrão: `15`
* **PinAuth**: opções da autenticação por PIN
* **MaxFailedAttempts**: número máximo de tentativas malsucedidas antes de a autenticação por PIN ser bloqueada. Padrão: `5`
* **LockPermanently**: se o bloqueio do PIN deve ser permanente. Padrão: `True`
* **InitialLockPeriodMinutes**: se o bloqueio não for permanente, define a duração do primeiro período de bloqueio, em minutos (cada bloqueio seguinte dura o dobro do anterior). Não tem valor padrão e é obrigatória quando **LockPermanently** é `False`.
* **PukAuth**: opções da autenticação por PUK
* **MaxFailedAttempts**: número máximo de tentativas malsucedidas antes de a autenticação por PUK ser bloqueada. Padrão: `5`
* **LockPermanently**: se o bloqueio do PUK deve ser permanente. Padrão: `True`
* **InitialLockPeriodMinutes**: se o bloqueio não for permanente, define a duração do primeiro período de bloqueio, em minutos (cada bloqueio seguinte dura o dobro do anterior). Não tem valor padrão e é obrigatória quando **LockPermanently** é `False`.
* **OtpAuth**: opções da autenticação por OTP
* **MaxFailedAttempts**: número máximo de tentativas malsucedidas antes de a autenticação por OTP ser bloqueada. Padrão: `5`
* **LockPermanently**: se o bloqueio do OTP deve ser permanente. Padrão: `False`
* **InitialLockPeriodMinutes**: se o bloqueio não for permanente, define a duração do primeiro período de bloqueio, em minutos (cada bloqueio seguinte dura o dobro do anterior). Padrão: `1`
* **NewClientsStepSeconds**: intervalo de tempo (*step*), em segundos, dos clientes recém-gerados. Padrão: `15`
* **VerificationWindow**: tamanho da janela de verificação (nos dois sentidos). Padrão: `1`
* **SmsAuth**: opções da autenticação por SMS
* **MaxFailedAttempts**: número máximo de tentativas malsucedidas antes de a autenticação por SMS ser bloqueada. Padrão: `5`
* **LockPermanently**: se o bloqueio do SMS deve ser permanente. Padrão: `False`
* **InitialLockPeriodMinutes**: se o bloqueio não for permanente, define a duração do primeiro período de bloqueio, em minutos (cada bloqueio seguinte dura o dobro do anterior). Padrão: `1`
* **CodeSize**: tamanho dos códigos de verificação. Padrão: `6`
* **PukPolicy**: requisitos mínimos para PUKs definidos pelo usuário
* **MinLength**: tamanho mínimo. Padrão: `8`
* **MinUppercaseLetters**: número mínimo de letras maiúsculas. Padrão: `0`
* **MinLowercaseLetters**: número mínimo de letras minúsculas. Padrão: `0`
* **MinDigits**: número mínimo de dígitos. Padrão: `0`
* **MinSpecialCaracters**: número mínimo de caracteres especiais. Padrão: `0`
> **Observação**
>
> Independentemente do valor da configuração **LockPermanently** da subseção **PinAuth**, uma autenticação por PUK bem-sucedida sempre desbloqueia a autenticação por PIN.
A política de PIN não é configurada na seção *CertificateManagement*, e sim na subseção **PinPolicy** da configuração do repositório de chaves. Se o
repositório de chaves for um HSM, a política de PIN deve corresponder à política de senhas de usuário configurada no HSM.
> **Dica**
>
> Todas as configurações têm valores padrão adequados, exceto a configuração **KeyStore** e a subseção **PinPolicy** da configuração do repositório de chaves, que
> precisam ser preenchidas. Fora isso, preencha apenas as configurações cujo valor padrão você tenha um bom motivo para alterar.
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[CertificateManagement]
KeyStore=MyHSM
[CertificateManagement:SmsAuth]
InitialLockPeriodMinutes=2.5
CodeSize=7
[CertificateManagement:PukPolicy]
MinUppercaseLetters=1
MinLowercaseLetters=1
MinDigits=1
[KeyStores:MyHSM:PinPolicy]
MinDigits=6
```
Exemplo (variáveis de ambiente):
```sh
CertificateManagement__KeyStore=MyHSM
CertificateManagement__SmsAuth__InitialLockPeriodMinutes=2.5
CertificateManagement__SmsAuth__CodeSize=7
CertificateManagement__PukPolicy__MinUppercaseLetters=1
CertificateManagement__PukPolicy__MinLowercaseLetters=1
CertificateManagement__PukPolicy__MinDigits=1
KeyStores__MyHSM__PinPolicy__MinDigits=6
```
Exemplo (arquivo de configuração *.json*):
```json
{
...,
"CertificateManagement": {
"KeyStore": "MyHSM",
"SmsAuth": {
"InitialLockPeriodMinutes": 2.5,
"CodeSize": 7
},
"PukPolicy": {
"MinUppercaseLetters": 1,
"MinLowercaseLetters": 1,
"MinDigits": 1
}
},
"KeyStores": {
"MyHSM": {
...,
"PinPolicy": {
"MinDigits": 6
}
}
},
...
}
```
## Serviço de emissão
A configuração **IssuingService** define qual serviço de emissão será usado para emitir os certificados hospedados no Amplia:
* `Local`: o próprio Amplia emite os certificados (padrão)
* `AmpliaReg`: uma instância do [Amplia Reg](https://docs.lacunasoftware.com/articles/amplia-reg) emite os certificados
* `PMCert`: uma instância do PMCert emite os certificados
### Serviço de emissão local
Para que o próprio Amplia emita os certificados, basta omitir a configuração **IssuingService**.
> **Aviso**
>
> O serviço de emissão local é experimental e não é adequado para ambientes de produção.
### Serviço de emissão do Amplia Reg
Para que uma instância do [Amplia Reg](https://docs.lacunasoftware.com/articles/amplia-reg) emita os certificados, crie, na sua instância do Amplia Reg, uma aplicação que
represente a instância do Amplia e gere uma chave de API para ela:
1. Entre na sua instância do Amplia Reg
1. Clique no seu nome, no canto superior direito, e depois em **Sys Admin**
1. Clique em **Aplicações** no menu lateral e depois em *Adicionar*
1. Preencha um nome
1. Marque o role **Trusted certificate issuing application**
1. Clique em *Criar*
1. Clique em **Chaves** e depois em *Adicionar*
1. Preencha uma descrição e, no campo *Expiração*, escolha "Nunca expira"
1. Clique em *Criar*
1. Copie a **chave de API** gerada (esse valor não pode ser recuperado depois)
Em seguida, defina **IssuingService** como `AmpliaReg` e preencha a seção **AmpliaReg** com o **Endpoint** e a **ApiKey**.
Exemplo (arquivo *.ini* ou *.conf*):
```ini
[CertificateManagement]
IssuingService=AmpliaReg
[AmpliaReg]
Endpoint=https://your-amplia-reg-instance.com/
ApiKey=YOUR_API_KEY
```
Exemplo (variáveis de ambiente):
```sh
CertificateManagement__IssuingService=AmpliaReg
AmpliaReg__Endpoint=https://your-amplia-reg-instance.com/
AmpliaReg__ApiKey=YOUR_API_KEY
```
Exemplo (configuração JSON):
```json
...,
"CertificateManagement": {
"IssuingService": "AmpliaReg",
...
},
"AmpliaReg": {
"Endpoint": "https://your-amplia-reg-instance.com/",
"ApiKey": "YOUR_API_KEY"
}
...
```
### Serviço de emissão do PMCert
Para que uma instância do **PMCert** emita os certificados, defina **IssuingService** como `PMCert` e preencha a seção **PMCert** com o **Endpoint**.
Exemplo (arquivo *.ini* ou *.conf*):
```sh
[CertificateManagement]
IssuingService=PMCert
[PMCert]
Endpoint=https://your-pm-cert-instance.com/
```
Exemplo (variáveis de ambiente):
```sh
CertificateManagement__IssuingService=PMCert
PMCert__Endpoint=https://your-pm-cert-instance.com/
```
Exemplo (configuração JSON):
```json
...,
"CertificateManagement": {
"IssuingService": "PMCert",
...
},
"PMCert": {
"Endpoint": "https://your-pm-cert-instance.com/"
}
...
```
# [Configuração de envio de e-mails - Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-email)
Para habilitar o envio de e-mails, preencha as seguintes configurações da seção **Email**:
* **Enabled**: defina como `True`
* **ServerHost**: nome do host do servidor SMTP
* **Username** e **Password**: se o servidor SMTP exigir autenticação, defina estas configurações
* **SenderAddress**: endereço de e-mail a ser usado como remetente (campo *from*)
* **SenderName**: nome a ser usado como nome do remetente (opcional)
* **ServerPort**: por padrão, a comunicação SMTP é feita pela porta 587. Defina esta configuração para usar uma porta diferente
* **EnableSsl**: por padrão, a comunicação SMTP é feita com SSL. Para desabilitar o SSL, defina esta configuração como `false`
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[Email]
Enabled=True
ServerHost=smtp.yourcompany.com
Username=YOUR_USERNAME
Password=YOUR_PASSWORD
SenderAddress=yourapp@yourcompany.com
SenderName=Your App
```
Exemplo (variáveis de ambiente):
```sh
Email__Enabled=True
Email__ServerHost=smtp.yourcompany.com
Email__Username=YOUR_USERNAME
Email__Password=YOUR_PASSWORD
Email__SenderAddress=yourapp@yourcompany.com
Email__SenderName=Your App
```
Exemplo (arquivo de configuração *.json*):
```json
"Email": {
"Enabled": true,
"ServerHost": "smtp.yourcompany.com",
"Username": "YOUR_USERNAME",
"Password": "YOUR_PASSWORD",
"SenderAddress": "yourapp@yourcompany.com",
"SenderName": "Your App"
},
```
## Veja também
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Configuração de redirecionamento para HTTPS - Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-https-redirect)
Se você tem um certificado SSL válido para a sua [instância on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia)
e habilitou o SSL, talvez queira redirecionar para o endpoint HTTPS os usuários que acessam a sua instância por HTTP.
Para habilitar o redirecionamento para HTTPS, altere a seção **Bindings** do arquivo de configuração:
* **HttpsMode**: defina como `RedirectPages`
* **SslPort**: se o seu endpoint HTTPS não estiver na porta 443, defina a porta aqui
## Veja também
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Configuração de OCSP - Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-ocsp)
Os certificados emitidos pelo [Amplia](https://docs.lacunasoftware.com/articles/amplia) podem incluir links para verificação OCSP no formato `http://your-ocsp-domain/ocsp/your-ca`.
Na configuração do Amplia, a parte `your-ocsp-domain` do link é chamada de **domínio de OCSP**. Essa configuração é opcional e
normalmente não é preenchida; nesse caso, os certificados são emitidos sem links de verificação OCSP.
Para incluir links de verificação OCSP nos certificados, você deve escolher um domínio de OCSP, por exemplo `ocsp.yourcompany.com`.
Esse domínio deve ser escolhido tendo em mente que ele **terá que ser mantido por um longo período** (por toda a vida útil dos
certificados emitidos na sua instância do Amplia, que normalmente é de vários anos).
> **Observação**
>
> Se a sua instância do Amplia ficar acessível publicamente, o domínio de OCSP pode ser o mesmo domínio em que o dashboard do Amplia
> será acessado. Caso não tenha certeza se a sua instância ficará ou não acessível publicamente, escolha um domínio de OCSP diferente
> do domínio do dashboard.
O domínio de OCSP escolhido deve ser criado nos servidores DNS (registros A ou CNAME) apontando para o servidor em que o Amplia será
instalado ou, se a sua instância não ficar acessível publicamente, para um servidor público com um proxy reverso que encaminhe as
requisições OCSP para a sua instância do Amplia.
> **Dica**
>
> Você não precisa de um certificado SSL para o seu domínio de OCSP, já que o X.509 recomenda que as requisições OCSP sejam enviadas
> por HTTP, e não por HTTPS.
## Configurar o Amplia para usar o domínio de OCSP escolhido
Depois de seguir os passos acima para configurar um domínio de OCSP, preencha a seção `Amplia` do arquivo de configuração da
seguinte forma:
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[Amplia]
OcspEnabled=True
OcspDomains=ocsp.yourcompany.com
```
Exemplo (variáveis de ambiente):
```bash
Amplia__OcspEnabled=True
Amplia__OcspDomains=ocsp.yourcompany.com
```
Exemplo (arquivo de configuração *.json*):
```json
...,
"Amplia": {
"OcspEnabled": true,
"OcspDomains": "ocsp.yourcompany.com",
...
},
...
```
A configuração `OcspDomains` é uma lista separada por vírgulas, portanto você pode ter vários domínios de OCSP.
# [Configuração de OpenID Connect - Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-oidc)
O [Amplia](https://docs.lacunasoftware.com/articles/amplia) requer uma assinatura do [GrantID](https://docs.lacunasoftware.com/articles/grant-id).
Você pode usar uma assinatura SaaS em [grantid.com](https://grantid.com/) ou [executar sua própria instância do GrantID](https://docs.lacunasoftware.com/articles/grant-id/on-premises).
## Configurando o GrantID
Acesse o portal do Console do GrantID e siga as instruções abaixo.
### Criar uma conexão de API
Na aba **Scopes**, clique em **New API Connection** para criar uma conexão de API para a aplicação, informando:
* **Name**: `amplia`
* **Display name**: `Amplia`
* **User Informations (claims)**: marque todos
### Aplicação frontend
Na aba **Applications**, clique em **New Application** e informe:
* **Name**: `Amplia`
* **App-id**: `amplia`
* **Type**: selecione `Implicit flow`.
Clique na aba **Settings** e defina:
* Marque a caixa de seleção `Allow Access tokens via browser?` (se ainda não estiver marcada)
* Desmarque as seguintes caixas de seleção (se marcadas): `Require consent?`, `Front-channel logout session required?` e `Back-channel logout session required?`
* **Application URL**: a URL da sua aplicação. Exemplo: `https://amplia.yourcompany.com`
* **Allowed URLs**:
* **Redirect**: você deve adicionar 4 URLs de redirecionamento baseadas na URL da sua aplicação:
* `` sem barra no final.
* `/silent-refresh.html`
* `/private/profile/refresh`
* `/private`
* **CORS origins**: `` sem barra no final.
* **Post Logout**: `` sem barra no final.
* **Allowed Identification Scopes**: marque todos
* **Allowed API scopes**: marque a caixa de seleção do escopo de API `Amplia`
* Marque as seguintes caixas de seleção: `Enable digital certificate login?`, `Prioritize digital certificate login?` e `Require digital certificate login?`
> **Aviso**
>
> Não se esqueça de salvar suas alterações clicando no botão ao final da página.
### Aplicação backend
Na aba **Applications**, clique novamente em **New Application** e informe:
* **Name**: `Amplia Backend`
* **App-id**: `amplia-backend`
* **Type**: selecione `Client credentials`.
Clique na aba **Settings** e defina:
* **Allowed API scopes**: marque a caixa de seleção `Manage subscription's users`.
> **Aviso**
>
> Não se esqueça de salvar suas alterações clicando no botão ao final da página.
Após salvar, retorne à aba **Basic Details** e, na seção **App Secret**, clique em **Generate** e depois em **Generate Secret**.
Este é o valor da configuração `AppSecret` (copie-o e guarde-o para usar depois).
## Configurando o Amplia para usar o GrantID
Após seguir as etapas acima para configurar o GrantID, preencha a seção **Oidc** do arquivo de configuração:
* **Enabled**: se a integração com OpenID Connect está habilitada — `True` (padrão) ou `False` (enquanto estiver como `False`, o acesso é feito com a senha de *root*)
* **Authority**: a autoridade OIDC (ex.: *https://login.id.patorum.com*)
* **ApiEndpoint**: o endpoint de API do servidor OIDC (ex.: *https://api.id.patorum.com*)
* **ApiName**: `amplia`
* **ClientAppId**: `amplia`
* **AppId**: `amplia-backend`
* **AppSecret**: o segredo gerado para a aplicação backend
* **RequireHttps** (opcional): defina como `false` se a instância do GrantID não usar HTTPS
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[Oidc]
Enabled=True
Authority=https://login.id.patorum.com
ApiEndpoint=https://api.id.patorum.com
ApiName=amplia
ClientAppId=amplia
AppId=amplia-backend
AppSecret=YOUR_BACKEND_APP_SECRET
```
Exemplo (variáveis de ambiente):
```bash
Oidc__Enabled=True
Oidc__Authority=https://login.id.patorum.com
Oidc__ApiEndpoint=https://api.id.patorum.com
Oidc__ApiName=amplia
Oidc__ClientAppId=amplia
Oidc__AppId=amplia-backend
Oidc__AppSecret=YOUR_BACKEND_APP_SECRET
```
Exemplo (arquivo de configuração *.json*):
```json
...,
"Oidc": {
"Enabled": true,
"Authority": "https://login.id.patorum.com",
"ApiEndpoint": "https://api.id.patorum.com",
"ApiName": "amplia",
"ClientAppId": "amplia",
"AppId": "amplia-backend",
"AppSecret": "YOUR_BACKEND_APP_SECRET"
},
...
```
## Transição do acesso de root
Se a sua instância do Amplia foi configurada inicialmente para usar o acesso de root, ao configurar o gerenciamento de usuários e tentar acessar a aplicação
com o seu usuário do GrantID, você provavelmente verá uma página informando que você não tem acesso ("no access"). Isso acontece porque todos os privilégios
são concedidos ao primeiro usuário que entra na aplicação, que, no seu caso, *não* foi o usuário com o qual você está entrando agora, e sim o usuário root.
Nesse caso, você precisa conceder privilégios a si mesmo manualmente, pelo banco de dados. Comece localizando o `Id` do seu usuário:
```sql
SELECT "Id", "Name", "EmailAddress"
FROM "Agents"
WHERE "Subject" IS NOT NULL
```
Em seguida, conceda a si mesmo o role de sys admin *Owner* (copie o `Id` do seu usuário para a consulta abaixo):
```sql
UPDATE "Agents"
SET "RootRoleFlags" = 1
WHERE "Id" = 'YOUR-USER-ID'
```
## Veja também
* [GrantID](https://docs.lacunasoftware.com/articles/grant-id)
# [Configuração de envio de SMS - Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-sms)
Se os usuários forem emitir certificados (pelo procedimento de emissão web ou mobile), é exigida uma confirmação por SMS para verificar a identidade
do usuário durante a emissão do certificado.
A seção **SMS** do arquivo de configuração configura o envio de SMS. As mensagens SMS podem ser enviadas usando diferentes *provedores*. A configuração
**Type** define qual provedor deve ser usado, e as demais configurações dependem do provedor escolhido:
* [Amazon SNS](https://aws.amazon.com/sns/)
* **Type**: `AmazonSNS`
* **Region**: o código da região da AWS, por exemplo `sa-east-1` ou `us-east-1` (veja os [códigos de região da AWS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html))
* **AccessKeyId**: o ID da chave de acesso
* **SecretAccessKey**: a chave de acesso secreta
* [Comtele](https://comtele.com.br/)
* **Type**: `Comtele`
* **AuthToken** (obrigatório): token de autenticação da API
* **Sender** (opcional): campo *Sender* (veja a [documentação do serviço](https://docs.comtele.com.br/#enviar-sms) para mais detalhes)
* **Endpoint** (padrão: `https://sms.comtele.com.br/api/`): preencha para usar um endpoint de API personalizado (observe que o sufixo `v2/send` é adicionado automaticamente *depois* do endpoint).
* [Eyou](https://www.eyou.com.br/)
* **Type**: `Eyou`
* **ApiKey** (obrigatório): chave de API obtida no dashboard do serviço
* **Username** (obrigatório): campo *username* obtido no dashboard do serviço
* **Password** (obrigatório): campo *password* obtido no dashboard do serviço
* **UserAgent** (opcional): valor do header user agent a ser enviado à API. Por padrão, é enviada uma string com o nome e a versão da aplicação.
* **CostCenter** (opcional): campo *CostCenter* a ser enviado à API
* **Endpoint** (padrão: `https://sendsms.eyou.com.br/api/sendSms`): preencha para usar um endpoint de API personalizado.
* Genérico
* **Type**: `Generic`
* **Endpoint**: o endpoint para o qual as informações do SMS serão enviadas.
* **AuthType** (padrão: `NoAuth`): define o tipo de autorização enviada na requisição. As opções disponíveis são `NoAuth`, `Basic`, `Bearer` e `ApiKey`.
* **Username**: o nome de usuário, se o AuthType for `Basic`.
* **Password**: a senha, se o AuthType for `Basic`.
* **BearerToken**: o token de portador (bearer token), se o AuthType for `Bearer`.
* **ApiKey**: a chave de API, se o AuthType for `ApiKey`.
* [Pontaltech](https://pontaltech.com.br/plataforma-sms/)
* **Type**: para envio de OTP/token, use `FastPontaltech`; para outras finalidades (ex.: marketing ou notificações gerais), use `Pontaltech`
* **User** (obrigatório): campo *user* obtido no dashboard do serviço
* **Password** (obrigatório): campo *password* obtido no dashboard do serviço
* **Endpoint** (padrão: `sms-api-pointer.pontaltech.com.br`): nome do host usado nas chamadas à API (a rota em si não é configurável).
* [SMS Empresa](https://www.smsempresa.com.br/)
* **Type**: `SmsEmpresa`
* **ChaveKey**: a *Chave Key* fornecida em *Configuração* > *Minha Conta*
* [SMS Token](https://www.smstoken.com.br/)
* **Type**: `SmsToken`
* **Key** (obrigatório): chave de API obtida no dashboard do serviço
* **Endpoint** (padrão: `https://api.smstoken.com.br/`): preencha para usar um endpoint de API personalizado (a rota em si não é configurável).
* [Total Voice](https://totalvoice.com.br/)
* **Type**: `TotalVoice`
* **AccessToken**: o token de acesso, fornecido pela TotalVoice (ex.: `0123456789abcdef0123456789abcdef`)
* [Twilio](https://www.twilio.com/)
* **Type**: `Twilio`
* **MessageFrom**: o número de telefone do remetente fornecido pela Twilio (ex.: `+12125550000`)
* **AccountSid**: o SID da conta, fornecido pela Twilio
* **AuthToken**: o token de autenticação, fornecido pela Twilio
* [Zenvia](https://www.zenvia.com/)
* **Type**: `Zenvia`
* **Account**: o nome da sua conta (ex.: `patorum.sms`)
* **Password**: a senha da API
* **Endpoint** (opcional): URL base alternativa da API (deixe em branco se não souber)
* Dump de e-mail (apenas para fins de depuração): este provedor simula o envio de mensagens SMS enviando-as como mensagens de e-mail. O número de destino
é usado como caixa de correio (sem o `+` inicial) e o domínio padrão é `mailinator.com`. Por exemplo, uma mensagem SMS para o número `+1 212 555-0001`
seria enviada, em vez disso, como uma mensagem de e-mail para `12125550001@mailinator.com`.
* **Type**: `EmailDump`
* **Domain**: o domínio a ser usado. O domínio padrão é `mailinator.com`, mas outros domínios de e-mail descartável podem ser usados (ex.: `grr.la`)
* **EmailSender**: o remetente de e-mail a ser usado. Por padrão, é usado o remetente de e-mail padrão. Normalmente, esta configuração não é alterada.
* **UseDefaultNamedEmailSender**: defina como `true` para usar o remetente de e-mail sem nome (o configurado diretamente na seção **Email**). Normalmente, esta configuração não é alterada.
* Simulador (apenas para fins de depuração): este provedor simula o envio de mensagens SMS registrando no log do sistema as mensagens que seriam enviadas
* **Type**: `Simulator`
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[SMS]
Type=AmazonSNS
Region=us-east-1
AccessKeyId=...
SecretAccessKey=...
```
Exemplo (variáveis de ambiente):
```ini
SMS__Type=AmazonSNS
SMS__Region=us-east-1
SMS__AccessKeyId=...
SMS__SecretAccessKey=...
```
Exemplo (arquivo de configuração *.json*):
```json
...,
"SMS": {
"Type": "AmazonSNS",
"Region": "us-east-1",
"AccessKeyId": "...",
"SecretAccessKey": "..."
},
...
```
## Usando vários serviços de envio de SMS
Para configurar vários serviços de envio de SMS, crie subseções nomeadas dentro da seção **SMS** e defina uma **Priority** para cada serviço. Quanto menor
o valor, maior a prioridade.
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[SMS:AmazonUS]
Type=AmazonSNS
Region=us-east-1
AccessKeyId=...
SecretAccessKey=...
Priority=1
[SMS:AmazonSA]
Type=AmazonSNS
Region=sa-east-1
AccessKeyId=...
SecretAccessKey=...
Priority=2
[SMS:Fallback]
Type=Twilio
MessageFrom=...
AccountSid=...
AuthToken=...
Priority=3
```
Exemplo (variáveis de ambiente):
```ini
SMS__AmazonUS__Type=AmazonSNS
SMS__AmazonUS__Region=us-east-1
SMS__AmazonUS__AccessKeyId=...
SMS__AmazonUS__SecretAccessKey=...
SMS__AmazonUS__Priority=1
SMS__AmazonSA__Type=AmazonSNS
SMS__AmazonSA__Region=sa-east-1
SMS__AmazonSA__AccessKeyId=...
SMS__AmazonSA__SecretAccessKey=...
SMS__AmazonSA__Priority=2
SMS__Fallback__Type=Twilio
SMS__Fallback__MessageFrom=...
SMS__Fallback__AccountSid=...
SMS__Fallback__AuthToken=...
SMS__Fallback__Priority=3
```
No exemplo acima, primeiro é usado o serviço principal *AmazonUS* (`Priority=1`); se ele falhar, é usado o serviço secundário *AmazonSA*
(`Priority=2`) e, depois, o serviço *Fallback* (`Priority=3`).
> **Observação**
>
> Para que um serviço secundário seja usado, o provedor precisa retornar um erro. Alguns provedores retornam sucesso mesmo quando a entrega da mensagem é
> rejeitada internamente. Nesse caso, a aplicação não tem como saber que a entrega falhou e, portanto, o provedor secundário não é usado.
## Veja também
* [Amplia on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises)
# [Propagação de LCRs - Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/crl-propagation)
> **Observação**
>
> Este artigo está em construção. Pedimos desculpas pelo inconveniente.
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[Amplia]
CrlPropagationBlobServices=AzureCrls,AwsCrls
CACertPropagationBlobServices=AzureCerts,AwsCerts
[BlobStorage:AzureCrls]
Type=Azure
ConnectionString=...
ContainerName=crls
[BlobStorage:AzureCerts]
Type=Azure
ConnectionString=...
ContainerName=certs
[BlobStorage:AwsCrls]
Type=AwsS3
Region=us-east-1
AccessKey=...
SecretKey=...
BucketName=crls
[BlobStorage:AwsCerts]
Type=AwsS3
Region=us-east-1
AccessKey=...
SecretKey=...
BucketName=certs
```
Exemplo (variáveis de ambiente):
```sh
Amplia__CrlPropagationBlobServices=AzureCrls,AwsCrls
Amplia__CACertPropagationBlobServices=AzureCerts,AwsCerts
BlobStorage__AzureCrls__Type=Azure
BlobStorage__AzureCrls__ConnectionString=...
BlobStorage__AzureCrls__ContainerName=crls
BlobStorage__AzureCerts__Type=Azure
BlobStorage__AzureCerts__ConnectionString=...
BlobStorage__AzureCerts__ContainerName=certs
BlobStorage__AwsCrls__Type=AwsS3
BlobStorage__AwsCrls__Region=us-east-1
BlobStorage__AwsCrls__AccessKey=...
BlobStorage__AwsCrls__SecretKey=...
BlobStorage__AwsCrls__BucketName=crls
BlobStorage__AwsCerts__Type=AwsS3
BlobStorage__AwsCerts__Region=us-east-1
BlobStorage__AwsCerts__AccessKey=...
BlobStorage__AwsCerts__SecretKey=...
BlobStorage__AwsCerts__BucketName=certs
```
Exemplo (arquivo de configuração *.json*):
```json
...
"Amplia": {
"CrlPropagationBlobServices": "AzureCrls,AwsCrls",
"CACertPropagationBlobServices": "AzureCerts,AwsCerts",
...
},
"BlobStorage": {
"AzureCrls": {
"Type": "Azure",
"ConnectionString": "...",
"ContainerName": "crls"
},
"AzureCerts": {
"Type": "Azure",
"ConnectionString": "...",
"ContainerName": "certs"
},
"AwsCrls": {
"Type": "AwsS3",
"Region": "us-east-1",
"AccessKey": "...",
"SecretKey": "...",
"BucketName": "crls"
},
"AwsCerts": {
"Type": "AwsS3",
"Region": "us-east-1",
"AccessKey": "...",
"SecretKey": "...",
"BucketName": "certs"
}
},
...
```
# [Migrando uma instância do Amplia com chaves armazenadas em banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/migrate-with-db-keys)
Ao migrar uma instância [on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) para outro ambiente, se a instância tiver chaves de AC
armazenadas no [repositório de chaves em banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/database), é preciso tomar cuidados adicionais para que essas chaves possam ser usadas no novo ambiente.
Se, depois de migrar a sua instância do Amplia, ocorrerem erros na emissão de certificados ou certificados forem recusados por "status de revogação desconhecido"
(unknown revocation status), ou se o log da aplicação contiver a exceção abaixo, provavelmente você precisa executar os passos descritos na próxima seção.
```text
System.Security.Cryptography.CryptographicException: The payload was invalid.
```
## Procedimento de migração
1. Certifique-se de que a instância do Amplia no ambiente de origem está executando pelo menos a versão [3.13.2](https://docs.lacunasoftware.com/articles/amplia/changelog) e de que a versão que você está
instalando no ambiente de destino é a mesma da versão do ambiente de origem (ou mais recente)
1. Entre no painel de controle **na origem**
1. Clique no seu nome, no canto superior direito da tela, e depois em **Administração** (Administration)
1. No menu lateral, clique em **Sobre** (About)
1. Anote o valor da propriedade **App Discriminator**
1. Ao instalar a nova instância, use esse mesmo valor na configuração `AppDiscriminator` da seção `General` (veja abaixo)
Exemplo (arquivo de configuração *.ini* ou *.conf*):
```ini
[General]
AppDiscriminator=PASTE_VALUE_HERE
```
Exemplo (variáveis de ambiente):
```sh
General__AppDiscriminator=PASTE_VALUE_HERE
```
Exemplo (arquivo de configuração *.json*):
```json
...,
"General": {
"AppDiscriminator": "PASTE_VALUE_HERE"
},
...
```
# [Preparando o banco de dados para instalação do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database)
Para instalar uma [instância *on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia), você vai precisar de uma *connection string* para um dos seguintes SGBDs:
* [SQL Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database#sql-server)
* [PostgreSQL](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database#postgres)
## SQL Server
Para usar SQL Server, você vai precisar de um banco de dados com:
* Collation: `Latin1_General_100_CI_AI`
* Credenciais correspondentes a um usuário com papel `db_owner`
> **Observação**
>
> Se você preferir operar o Amplia sem conceder `db_owner` ao usuário da aplicação, siga as instruções [neste artigo](https://docs.lacunasoftware.com/articles/amplia/on-premises/unprivileged-db-user)
> **Aviso**
>
> A *collation* do banco de dados **PRECISA SER** `Latin1_General_100_CI_AI`. Criar o banco de dados com uma *collation* diferente provavelmente fará com que a instalação falhe!
Se você precisar de ajuda para preparar o banco de dados, siga os passos neste artigo.
> **Observação**
>
> Você não precisa seguir estas instruções especifícas. Se você desejar preparar o banco de dados,
> por exemplo usando recursos avançados como *log shipping* ou *mirroring*, você pode fazê-lo, desde que a *collation* e as permissões sejam observadas.
Para criar o banco de dados, use a seguinte consulta T-SQL (opcionalmente alterando o nome do banco de dados):
```sql
USE Master;
CREATE DATABASE Amplia COLLATE Latin1_General_100_CI_AI;
GO
```
Crie um usuário e associe-o ao papel `db_owner`:
```sql
USE master;
CREATE LOGIN AmpliaAdm WITH PASSWORD = 'XXXXX';
GO
USE Amplia;
CREATE USER AmpliaAdm FOR LOGIN AmpliaAdm;
EXEC sp_addrolemember 'db_owner', 'AmpliaAdm';
GO
```
A *connection string* seria, então:
```text
Data Source=.;Initial Catalog=Amplia;User ID=AmpliaAdm;Password=XXXXX
```
> **Observação**
>
> Essa *connection string* presume que o servidor do banco de dados esteja instalado no mesmo servidor que o aplicativo da web. Se este não for o caso,
> o valor após `Data Source=` deve ser alterado.
## PostgreSQL
A partir da [versão 4.1.0](https://docs.lacunasoftware.com/articles/amplia/changelog#v4-1-0) do Amplia, também é suportado o uso de PostgreSQL. Qualquer uma das versões do PostgreSQL dentro do período
de suporte oficial também é suportada ([o que, no momento, significa versão 11 ou superior](https://www.postgresql.org/support/versioning/)).
Comece criando o banco de dados e um usuário para a aplicação (altere os nomes caso deseje):
```text
postgres=# CREATE DATABASE amplia;
CREATE DATABASE
postgres=# CREATE USER amplia WITH PASSWORD 'XXXXX';
CREATE ROLE
```
Em seguida, conecte-se ao banco de dados recém-criado e configure as permissões ao *schema* `public`:
```text
postgres=# \connect amplia
You are now connected to database "amplia" as user "postgres".
amplia=# REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE
amplia=# GRANT ALL ON SCHEMA public TO amplia;
GRANT
amplia=# CREATE SCHEMA hangfire;
CREATE
amplia=# GRANT ALL ON SCHEMA hangfire TO amplia;
GRANT
```
> **Observação**
>
> Por ora, é necessário utilizar um usuário com acesso pleno. Contate-nos se você precisar executar o Amplia usando um usuário com acesso limitado.
A *connection string* seria, então, a seguinte (assumindo que você não alterou os nomes do banco de dados e do usuário):
```text
Host=localhost;Database=amplia;Username=amplia;Password=XXXXX
```
> **Observação**
>
> Essa *connection string* parte do pressuposto que o SGBD está instalado no mesmo servidor que a aplicação web. Se esso não for o caso, o valor
> depois de `Host=` acima teria que ser alterado.
## See also
* [Operando o Amplia sem ser *owner* do banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/unprivileged-db-user)
# [Operando o Amplia sem ser *owner* do banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/unprivileged-db-user)
O [Amplia](https://docs.lacunasoftware.com/articles/amplia) pode operar de duas maneiras em relação ao acesso do banco de dados:
1. Sendo *owner* do banco de dados e atualizar automaticamente o modelo do banco de dados após uma atualização quando necessário (padrão)
1. Ter privilégios apenas de leitura e escrita no banco de dados, exigindo que o modelo do banco de dados seja atualizado pelo administrador usando uma ferramenta de linha de comando
Operar com o usuário da aplicação sendo *owner* do aplicativo é mais simples, por isso essa é a opção sugerida na instalação padrão. Entretanto, conceder somente privilégios de leitura e escrita
garante ao administrador maior controle sobre o banco de dados. Se você preferir operar o Amplia sem privilégios de *owner* para o usuário da aplicação, siga os passos descritos nesse artigo.
## Criando credenciais de leitura e escrita
Crie o banco de dados normalmente conforme descrito na [preparação do banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database).
Em seguida, ao invés de criar um usuário chamado *AmpliaAdm* e associá-lo ao papel `db_owner`, crie um usuário limitado chamado **AmpliaApp**
e associe-o apenas aos papeis `db_datareader` e `db_datawriter`:
```sql
USE master;
CREATE LOGIN AmpliaApp WITH PASSWORD = 'XXXXXX';
GO
USE Amplia;
CREATE USER AmpliaApp FOR LOGIN AmpliaApp;
EXEC sp_addrolemember 'db_datareader', 'AmpliaApp';
EXEC sp_addrolemember 'db_datawriter', 'AmpliaApp';
GO
```
A *connection string* seria, então:
```text
Data Source=.;Initial Catalog=Amplia;User ID=AmpliaApp;Password=XXXXX
```
> **Observação**
>
> Essa *connection string* presume que o servidor do banco de dados esteja instalado no mesmo servidor que o aplicativo da web. Se este não for o caso,
> o valor após `Data Source=` deve ser alterado.
## Desabilitando a atualização automática do banco de dados
Operar com o Amplia sem privilégios de *owner* sobre o banco de dados significa que a aplicação não será capaz de atualizar o banco por conta própria.
Por isso, é preciso desabilitar a atualização automática do banco de dados de modo a evitar erros.
Edite o arquivo JSON de configuração do Amplia (a localização do arquivo depende da plataforma onde a sua instância está instalada) e adicione:
* Seção **General**
* **AutoUpdateDatabase**: atribua o valor `false`
## Veja também
* [Preparando o banco de dados para instalação do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/prepare-database)
# [Atualizando o Amplia da versão 2.16 para 3.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-30)
Para atualizar sua instância [*on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) da versão 2.16.x para a [versão 3.0](https://docs.lacunasoftware.com/articles/amplia/changelog#v3-0-0), algumas
alterações são necessárias.
## ASP.NET Core Environment
A partir da versão 3.0, o Amplia requer que a variável de ambiente `ASPNETCORE_ENVIRONMENT` esteja setada com `Windows`, `Linux` ou `Azure`.
Em Windows Server, execute o comando abaixo em um *command prompt* com privilégios de administrador:
```sh
%SYSTEMROOT%\System32\inetsrv\appcmd.exe set config Amplia -section:system.webServer/aspNetCore /+"environmentVariables.[name='ASPNETCORE_ENVIRONMENT',value='Windows']" /commit:apphost
```
Em Azure App Services, vá na aba **Configuração** do seu app service e adicione uma configuração com nome `ASPNETCORE_ENVIRONMENT` e valor `Azure`.
> **Observação**
>
> Em Linux, essa variável já era setada como parte do processo de instalação das versões 2.x. Portanto, não há nada a fazer.
## Mudanças de configuração
* Seção `Oidc`
* `DashboardClientId` → `ClientAppId`
* Preencha as configurações `ApiEndpoint`, `AppId` e `AppSecret` (verifique com o suporte técnico os valores apropriados para a sua instância)
* Altere a configuração da aplicação no GrantID, alterando a *allowed redirect URLs* de `https://your-amplia-domain/dashboard` para apenas `https://your-amplia-domain` i.e. remova o sufixo `/dashboard` (mantenha a *return URL* com final *silent-refresh.html*, ela ainda é necessária)
* Na seção `PkiSuite`, se você possui alguma configuração `Mobile*` (por exemplo `MobileAppName`), crie uma seção chamada `MobileApp` e mova
essas configurações para essa nova seção, renomeando-as conforme abaixo:
* `PkiSuite:MobileAppName` → `MobileApp:Name`
* `PkiSuite:MobileAppCodeSuffix` → `MobileApp:CodeSuffix`
* `PkiSuite:MobileIOSStoreUri` → `MobileApp:IOSStoreUri`
* `PkiSuite:MobileAndroidStoreUri` → `MobileApp:AndroidStoreUri`
Em Azure App Services:
* Se você possui um *log sink* do tipo *AzureTableStorageWithProperties*, altere a configuração `keyGenerator` para `Lacuna.Spa.Engine.AzureTableKeyGenerator, Lacuna.Spa.Engine`
* Se você possuir uma seção chamada `AzureStorage`, renomeie-a para `BlobStorage`
# [Atualizando o Amplia da versão 3.0-3.4 para 3.5](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-35)
> **Observação**
>
> Se a sua instância do Amplia está em uma versão 2.x, [atualize primeiro para a versão 3.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-30)
Com o término do suporte por parte da Microsoft ao ASP.NET Core 2.2, a versão [3.5](https://docs.lacunasoftware.com/articles/amplia/changelog#v3-5-0) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) foi migrada
para o ASP.NET Core 3.1, que é a versão mais recente com suporte de longo prazo, [sendo suportada até dezembro de 2022](https://dotnet.microsoft.com/platform/support/policy/dotnet-core#lifecycle).
Por isso, para atualizar a sua instância [*on premises*](https://docs.lacunasoftware.com/articles/amplia/on-premises), você precisará instalar o ASP.NET Core Runtime 3.1.
Veja a seção *Instale o ASP.NET Core Runtime 3.1* do procedimento de instalação correspondente ao ambiente onde sua instância encontra-se instalada:
* [Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/install#install-aspnet-core)
* Linux
* [Ubuntu Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-ubuntu#install-aspnet-core)
* [Red Hat Enterprise Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-rhel#install-aspnet-core)
* [Oracle Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-oracle#install-aspnet-core)
> **Observação**
>
> Em Docker não há nada a fazer, a nova imagem 3.5 já vem com o ASP.NET Core 3.1 embutido
## Azure App Services
Se a sua instância do Amplia está instalada em Azure App Services:
1. No Azure Portal, acesse a configuração do App Service
1. Clique em **Configuration**, em seguida na aba **General settings**
1. Na seção *Stack settings*:
1. A configuração *Stack* deve estar em **.NET Core**
1. Se houver o campo *Major version*, selecione **3.1**
> **Dica**
>
> O campo *Major version* só aparece se o App Service Plan for Linux. Caso contrário, no caso de App Service Plans em Windows, não é necessário
> alterar a *major version*.
# [Atualizando o Amplia da versão 3.x para 4.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-40)
> **Observação**
>
> Se a sua instância do Amplia estiver em uma versão 2.x, [atualize primeiro para a 3.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-30)
> **Aviso**
>
> Esta atualização cria dois índices que faltavam, nas tabelas `Orders` e `Certificates`. Se o seu banco de dados tiver um número elevado de pedidos e/ou
> certificados (vários milhares ou mais), a atualização do banco de dados pode falhar. Nesse caso, atualize o banco de dados manualmente com o comando
> [update-db](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/update-db), passando um valor alto em `--timeout `.
Como o ASP.NET Core 3.1 foi descontinuado, a versão [4.0](https://docs.lacunasoftware.com/articles/amplia/changelog#v4-0-0) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) foi migrada para o ASP.NET Core 6.0, que é
a versão LTS mais recente do ASP.NET Core, [com suporte da Microsoft até novembro de 2024](https://dotnet.microsoft.com/platform/support/policy/dotnet-core#lifecycle).
Por isso, para atualizar a sua instância [on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises), você precisa instalar o ASP.NET Core Runtime 6.0.
Consulte a seção de instalação do ASP.NET Core Runtime 6.0 do procedimento de instalação correspondente à sua plataforma:
* [Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/install#install-aspnet-core)
* Linux
* [Ubuntu Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-ubuntu#install-aspnet-core)
* [Red Hat Enterprise Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-rhel#install-aspnet-core)
* [Oracle Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-oracle#install-aspnet-core)
> **Observação**
>
> No Docker, não há nada a fazer: a nova imagem 4.0 já é baseada na imagem do ASP.NET Core 6.0
## Azure App Services
Se a sua instância do Amplia estiver hospedada no Azure App Services:
1. No portal do Azure, acesse a configuração do seu App Service
1. Clique em **Configuration** e selecione a aba **General settings**
1. Na seção *Stack settings*:
1. A configuração *Stack* deve estar definida como **.NET**
1. A configuração *.NET version* deve estar definida como **.NET 6 (LTS)**
> **Dica**
>
> A seção *Stack settings* mencionada acima só é exibida se o App Service Plan for baseado em Windows. Em planos baseados em Linux, nenhuma alteração é necessária.
# [Atualizando o Amplia da versão 4.x para 5.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-50)
A versão [5.0](https://docs.lacunasoftware.com/articles/amplia/changelog#v5-0-0) do [Amplia](https://docs.lacunasoftware.com/articles/amplia) foi migrada para o ASP.NET Core 10.0, a versão LTS mais recente do ASP.NET Core,
[com suporte da Microsoft até novembro de 2028](https://dotnet.microsoft.com/platform/support/policy/dotnet-core#lifecycle).
Por isso, para atualizar a sua instância [on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises), você precisa instalar o ASP.NET Core Runtime 10.0 antes de instalar a nova versão.
Consulte a seção de instalação do ASP.NET Core Runtime 10.0 do procedimento de instalação correspondente à sua plataforma:
* [Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/install#install-aspnet-core) (ASP.NET Core 10.0 Hosting Bundle)
* Linux
* [Ubuntu Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-ubuntu#install-aspnet-core)
* [Red Hat Enterprise Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-rhel#install-aspnet-core)
* [Oracle Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-oracle#install-aspnet-core)
* [Rocky Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-rocky#instale-o-aspnet-core-runtime-100)
> **Observação**
>
> O .NET 10 não oferece mais suporte a algumas versões antigas de sistemas operacionais, como Red Hat Enterprise Linux 7 e Oracle Linux 7. Se o seu servidor
> usa uma delas, migre a instância para uma [versão suportada](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux) antes de atualizar.
> **Observação**
>
> No Docker, não há nada a fazer: a imagem 5.0 já é baseada na imagem do ASP.NET Core 10.0. Use a imagem `lacunasoftware/amplia:5.0`
> (veja [Setup do Amplia em Docker](https://docs.lacunasoftware.com/articles/amplia/on-premises/docker)).
Em seguida, atualize o Amplia normalmente:
* [Atualização do Amplia em Windows Server](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/update)
* [Atualização do Amplia em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/update)
* [Atualização do Amplia em Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/update)
## Atualização do modelo do banco de dados
Esta atualização altera o modelo do banco de dados. Por padrão, o Amplia atualiza o modelo do banco de dados sozinho ao iniciar. Se o Amplia opera sem ser
*owner* do banco de dados, atualize o modelo do banco de dados com o comando [update-db](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/update-db) antes de iniciar a nova versão (veja
[Operando o Amplia sem ser *owner* do banco de dados](https://docs.lacunasoftware.com/articles/amplia/on-premises/unprivileged-db-user)).
## Métricas do Prometheus
O Amplia 5.0 não expõe mais métricas para o Prometheus (a integração experimental adicionada na [4.13.0](https://docs.lacunasoftware.com/articles/amplia/changelog#v4-13-0)). Se você coleta essas
métricas, remova o Amplia da configuração do seu Prometheus.
## Key stores Azure Key Vault
O [key store Azure Key Vault](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/azure) agora usa a versão 4 do Azure SDK. As configurações existentes (**AppId** com **AppKey** ou **CertThumb**)
continuam funcionando. O Amplia 5.0 também adiciona a configuração **UseManagedIdentity**, para autenticar com a identidade gerenciada do host em vez de uma
chave secreta ou certificado do aplicativo.
## Azure App Services
Se a sua instância do Amplia está hospedada no Azure App Services como descrito em [Instalação do Amplia em Azure App Services](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/install)
(App Service Plan Linux executando a imagem Docker), nenhuma alteração nas configurações de *stack* é necessária: [importe a imagem 5.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/azure/update) e
selecione a tag dela.
Se a sua instância usa um App Service Plan baseado em Windows, na seção *Stack settings* de **Configuration** > **General settings**, defina a configuração
*Stack* como **.NET** e a configuração *.NET version* como .NET 10.
## Veja também
* [Histórico de versões do Amplia](https://docs.lacunasoftware.com/articles/amplia/changelog)
* [Verificando a versão instalada do Amplia](https://docs.lacunasoftware.com/articles/amplia/on-premises/check-version)
* [Atualizando o Amplia da versão 3.x para 4.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-40)
# [Guia de Operação - Amplia](https://docs.lacunasoftware.com/articles/amplia/operation)
Os artigos abaixo explicam como realizar as operações mais comuns no [Amplia](https://docs.lacunasoftware.com/articles/amplia), independentemente se você está o usando como um serviço (SaaS)
ou rodando [sua própria instância](https://docs.lacunasoftware.com/articles/amplia/on-premises).
* [Criação de AC raiz](https://docs.lacunasoftware.com/articles/amplia/operation/create-root-ca)
* [Criação de AC intermediária](https://docs.lacunasoftware.com/articles/amplia/operation/create-root-ca)
* [Criação de AC de dispositivos](https://docs.lacunasoftware.com/articles/amplia/operation/create-root-ca)
## Veja também
* [Amplia](https://docs.lacunasoftware.com/articles/amplia)
* [Histórico de versões](https://docs.lacunasoftware.com/articles/amplia/changelog)
# [Criação de AC para certificados de dispositivo - Amplia](https://docs.lacunasoftware.com/articles/amplia/operation/create-device-ca)
Alguns módulos da Lacuna -- em especial o [Amplia Reg](https://docs.lacunasoftware.com/articles/amplia-reg) -- dependem de uma Autoridade Certificadora no Amplia para emitir certificados
para dispositivos autorizados por um supervisor.
## Configure o Amplia
Comece entrando na sua instância do Amplia e selecionando a subscription usada pelo módulo dependente. Em seguida, siga os passos
abaixo.
### Crie uma chave
No menu lateral, clique em **Chaves** (Keys), depois em **Gerar** (Generate) e preencha:
* **Nome** (Name): `device-ca` ou um nome de sua preferência (recomenda-se *kebab-case* para nomear chaves)
* **Repositório de chaves** (Key Store): selecione um repositório de chaves para armazenar a chave da AC. Se estiver em dúvida, escolha **Database**
* **Tipo de chave** (Key Type): `RSA`
* **Tamanho** (Size): 4096
Clique em **Criar** (Create).
### Crie uma AC raiz
No menu lateral, clique em **ACs** (CAs), depois em **Criar AC raiz** (Create Root CA) e preencha:
* **Chave** (Key): selecione a chave que você acabou de gerar (`device-ca`)
* **Nome** (Name): o formulário vai sugerir o mesmo nome da chave; não altere o valor sugerido sem um bom motivo
* **Data de expiração** (Expiration Date): o formulário vai sugerir uma data daqui a 30 anos, o que normalmente é uma boa escolha
* **Estratégia de tipo de nome** (Name Type Strategy): escolha `Utf8StringsWhenNecessary`
* **Nome comum** (Common Name): o campo `CN=` do certificado da AC. Sugestão: `YOUR COMPANY Device CA`
* **Unidades organizacionais** (Organizational Units): o campo `OU=` do certificado da AC. Normalmente fica em branco.
* **Organização** (Organization): o nome completo da sua organização, por exemplo `Your Company S.A.`
* **País** (Country): selecione o país da sua organização
Clique em **Criar** (Create) e depois em **OK**.
Depois que a AC for criada, clique em **Ativar** (Activate) e depois em **Sim** (Yes).
Na página de detalhes da AC, copie a propriedade **Id**. Esse é o seu `CAId` (você vai precisar desse valor para configurar o módulo que exige a AC).
### Crie um template
No menu lateral, clique em **Templates**, depois em **Criar** (Create) e preencha:
* **Nome** (Name): `Device certificate`
* **Tipo de certificado** (Certificate kind): `Public Key`
* **Formato do certificado** (Certificate format): `Custom`
Clique em **Criar** (Create). Depois que o template for criado, clique em **Editar** (Edit) e preencha:
* **Validade (dias)** (Validity (days)): normalmente `365`, mas você pode escolher outro valor
* **OID da política de certificado** (Certificate Policy OID): `1.3.6.1.4.1.46332.3.3` (a [política de certificado de autenticação de dispositivos](https://docs.lacunasoftware.com/articles/asn1#certificate-policies) da Lacuna)
* **Tamanho mínimo da chave** (Minimum key size): `2048`
No campo **Parâmetros específicos do template (JSON)** (Template-specific parameters (JSON)), apague todo o texto existente e cole o seguinte:
```json
{
"format": "Custom",
"keyUsages": [
"DigitalSignature"
],
"allowCustomKeyUsages": false,
"extendedKeyUsages": [
"ClientAuth"
],
"allowCustomExtendedKeyUsages": false,
"typeStrategy": "Utf8StringsWhenNecessary"
}
```
Clique em **Salvar** (Save).
Na página de detalhes do template, copie a propriedade **Id**. Esse é o seu `TemplateId` (você vai precisar desse valor para configurar o módulo que exige a AC).
## Configure o módulo dependente
Siga as instruções do módulo que exige a AC para certificados de dispositivo para definir o
[CAId](https://docs.lacunasoftware.com/articles/amplia/operation/create-device-ca#crie-uma-ac-raiz) e o [TemplateId](https://docs.lacunasoftware.com/articles/amplia/operation/create-device-ca#crie-um-template) no arquivo de configuração do módulo.
# [Criação de AC intermediária - Amplia](https://docs.lacunasoftware.com/articles/amplia/operation/create-intermediate-ca)
Para criar uma **autoridade certificadora intermediária** no [Amplia](https://docs.lacunasoftware.com/articles/amplia), escolha o cenário que melhor descreve o seu caso:
* Se você controla tanto a AC superior quanto a AC intermediária...
* ... e elas estão hospedadas na mesma instância do Amplia, veja o [Cenário centralizado](https://docs.lacunasoftware.com/articles/amplia/operation/create-intermediate-ca#cenário-centralizado)
* ... e elas estão hospedadas em instâncias diferentes do Amplia, veja o [Cenário distribuído](https://docs.lacunasoftware.com/articles/amplia/operation/create-intermediate-ca#cenário-distribuído)
* Se você controla apenas a AC intermediária ou apenas a AC superior, veja [Outros cenários](https://docs.lacunasoftware.com/articles/amplia/operation/create-intermediate-ca#outros-cenários)
> **Observação**
>
> *AC superior* (parent CA) é a AC sob a qual a AC intermediária será criada. A menos que o seu caso envolva vários níveis de
> ACs intermediárias, trata-se simplesmente da AC raiz.
## Cenário centralizado
Você pode hospedar a sua AC intermediária na mesma instância do Amplia que a AC superior:

Nesse caso, siga os passos abaixo.
### 1. Crie a chave da AC
No menu lateral, clique em **Chaves** (Keys), depois em **Gerar** (Generate) e preencha:
* **Nome** (Name): `ca` ou um nome de sua preferência (recomenda-se `kebab-case` para nomear chaves)
* **Repositório de chaves** (Key Store): selecione um repositório de chaves para armazenar a chave da AC. Se estiver em dúvida, escolha **Database**
* **Tipo de chave** (Key Type): normalmente `RSA`
* **Tamanho** (Size): normalmente 4096
Clique em **Criar** (Create).
### 2. Emita o certificado da AC
No menu lateral, clique em **Certificados de AC** (CA Certificates), depois em **Emitir certificado de AC** (Issue CA Certificate) e preencha:
* **Emissor** (Issuer): selecione a AC superior (em geral, a sua AC raiz)
* Mantenha selecionada a opção **Chave interna** (Internal key)
* **Chave** (Key): selecione a chave que você acabou de gerar
Preencha os demais campos de acordo com o seu caso e clique em **Emitir** (Issue).
### 3. Crie a AC
No menu lateral, clique em **ACs** (CAs), depois em **Criar AC** (Create CA) e preencha:
* **Chave** (Key): selecione a chave que você acabou de gerar
* **Nome** (Name): o formulário vai sugerir o mesmo nome da chave; não altere o valor sugerido sem um bom motivo
* Mantenha selecionada a opção **AC de chave pública** (Public Key CA)
* Mantenha selecionada a opção **Certificado interno** (Internal Certificate)
* **Certificado** (Certificate): selecione o certificado que você acabou de emitir
Clique em **Criar** (Create).
Depois que a AC for criada, clique em **Ativar** (Activate) e depois em **Sim** (Yes).
## Cenário distribuído
Você também pode hospedar a sua AC intermediária enquanto a AC superior fica hospedada em outra instância do Amplia:

Nesse caso, siga os passos abaixo.
### 1. Crie a chave da AC e gere uma CSR (instância da AC intermediária)
Na instância da AC intermediária, no menu lateral, clique em **Chaves** (Keys), depois em **Gerar** (Generate) e preencha:
* **Nome** (Name): `ca` ou um nome de sua preferência (recomenda-se `kebab-case` para nomear chaves)
* **Repositório de chaves** (Key Store): selecione um repositório de chaves para armazenar a chave da AC. Se estiver em dúvida, escolha **Database**
* **Tipo de chave** (Key Type): normalmente `RSA`
* **Tamanho** (Size): normalmente 4096
Clique em **Criar** (Create).
Na página de detalhes da chave, clique em **Mostrar CSR** (Show CSR) e copie o conteúdo da CSR para a área de transferência.
### 2. Emita o certificado da AC (instância da AC superior)
Na instância da AC superior, no menu lateral, clique em **Certificados de AC** (CA Certificates), depois em **Emitir certificado de AC** (Issue CA Certificate) e preencha:
* **Emissor** (Issuer): selecione a AC superior (em geral, a sua AC raiz)
* Selecione **Chave externa** (External Key)
* **CSR**: cole a CSR da chave da AC intermediária
Preencha os demais campos de acordo com o seu caso e clique em **Emitir** (Issue). Em seguida, clique em **Baixar** (Download) e salve o arquivo do certificado da AC.
### 3. Crie a AC (instância da AC intermediária)
De volta à instância da AC intermediária, no menu lateral, clique em **ACs** (CAs), depois em **Criar AC** (Create CA) e preencha:
* **Chave** (Key): selecione a chave que você acabou de gerar
* **Nome** (Name): o formulário vai sugerir o mesmo nome da chave; não altere o valor sugerido sem um bom motivo
* Mantenha selecionada a opção **AC de chave pública** (Public Key CA)
* Selecione **Certificado externo** (External Certificate)
* Faça upload do arquivo do certificado da AC que você acabou de baixar
Clique em **Criar** (Create).
Depois que a AC for criada, clique em **Ativar** (Activate) e depois em **Sim** (Yes).
## Outros cenários
Também há cenários em que você controla apenas a AC intermediária ou apenas a AC superior, enquanto a outra AC é controlada por um terceiro. Nesses casos,
o procedimento é semelhante ao do [cenário distribuído](https://docs.lacunasoftware.com/articles/amplia/operation/create-intermediate-ca#cenário-distribuído), com a diferença de que alguns passos são executados pelo terceiro.
### AC intermediária sob a AC superior de um terceiro
Neste cenário, você controla apenas a AC intermediária, mas não a AC superior, por exemplo se estiver criando uma AC intermediária sob a AC raiz de um país:

Nesse caso, os passos 1 e 3 são de sua responsabilidade, enquanto o passo 2 é executado pelo terceiro:
1. [Crie a chave da AC e gere uma CSR](https://docs.lacunasoftware.com/articles/amplia/operation/create-intermediate-ca#1-crie-a-chave-da-ac-e-gere-uma-csr-instância-da-ac-intermediária) e envie a CSR ao terceiro
1. O terceiro fica responsável por emitir o certificado da AC com a sua CSR e por entregar a você o arquivo do certificado da AC
1. Com o arquivo do certificado da AC, [crie a AC](https://docs.lacunasoftware.com/articles/amplia/operation/create-intermediate-ca#3-crie-a-ac-instância-da-ac-intermediária)
### AC intermediária de um terceiro sob a sua AC superior
Neste cenário, você controla apenas a AC superior, mas não a AC intermediária, ou seja, um terceiro está criando uma AC intermediária sob a sua própria AC hospedada no
Amplia, por exemplo se você usa o Amplia para hospedar a AC raiz de um país:

Nesse caso, apenas o passo 2 é de sua responsabilidade, enquanto os passos 1 e 3 são executados pelo terceiro:
1. O terceiro é responsável por gerar a chave da AC e a CSR correspondente
1. Você então [emite o certificado da AC](https://docs.lacunasoftware.com/articles/amplia/operation/create-intermediate-ca#2-emita-o-certificado-da-ac-instância-da-ac-superior) com a CSR recebida e envia o arquivo do certificado da AC de volta ao terceiro
1. O terceiro usa o arquivo do certificado da AC para criar a AC
## Veja também
* [Criação de AC raiz](https://docs.lacunasoftware.com/articles/amplia/operation/create-root-ca)
* [Guia de operação](https://docs.lacunasoftware.com/articles/amplia/operation)
# [Criação de AC raiz - Amplia](https://docs.lacunasoftware.com/articles/amplia/operation/create-root-ca)
Para criar uma **autoridade certificadora raiz**, comece entrando na sua instância do [Amplia](https://docs.lacunasoftware.com/articles/amplia) e selecionando uma subscription no canto superior direito. Em seguida,
siga os passos abaixo.
### Crie a chave da AC raiz
No menu lateral, clique em **Chaves** (Keys), depois em **Gerar** (Generate) e preencha:
* **Nome** (Name): `root` ou um nome de sua preferência (recomenda-se `kebab-case` para nomear chaves)
* **Repositório de chaves** (Key Store): selecione um repositório de chaves para armazenar a chave da AC. Se estiver em dúvida, escolha **Database**
* **Tipo de chave** (Key Type): normalmente `RSA`
* **Tamanho** (Size): normalmente 4096
Clique em **Criar** (Create).
### Crie a AC raiz
No menu lateral, clique em **ACs** (CAs), depois em **Criar AC raiz** (Create Root CA) e preencha:
* **Chave** (Key): selecione a chave que você acabou de gerar (`root`)
* **Nome** (Name): o formulário vai sugerir o mesmo nome da chave; não altere o valor sugerido sem um bom motivo
* **Data de expiração** (Expiration Date): o formulário vai sugerir uma data daqui a 30 anos, o que normalmente é uma boa escolha
* **Estratégia de tipo de nome** (Name Type Strategy): escolha `Utf8StringsWhenNecessary`
* **Nome comum** (Common Name): o campo `CN=` do certificado da AC. Sugestão: `YOUR COMPANY Root CA`
* **Unidades organizacionais** (Organizational Units): o campo `OU=` do certificado da AC. Normalmente fica em branco.
* **Organização** (Organization): o nome completo da sua organização, por exemplo `Your Company S.A.`
* **País** (Country): selecione o país da sua organização
Clique em **Criar** (Create) e depois em **OK**.
Depois que a AC for criada, clique em **Ativar** (Activate) e depois em **Sim** (Yes).
## Veja também
* [Criação de AC intermediária](https://docs.lacunasoftware.com/articles/amplia/operation/create-intermediate-ca)
* [Guia de operação](https://docs.lacunasoftware.com/articles/amplia/operation)
# [Sobre - Amplia](https://docs.lacunasoftware.com/articles/amplia/about)
Os artigos abaixo contém descrições técnicas de algumas das características do [Amplia](https://docs.lacunasoftware.com/articles/amplia):
* [Geração de números de série](https://docs.lacunasoftware.com/articles/amplia/about/serial-number-generation)
## Veja também
* [Amplia *on premises**](https://docs.lacunasoftware.com/articles/amplia/on-premises) (guia de administração)
* [Guia de Operação](https://docs.lacunasoftware.com/articles/amplia/operation)
# [Sobre a geração de números de série - Amplia](https://docs.lacunasoftware.com/articles/amplia/about/serial-number-generation)
Os certificados de usuário final emitidos pelo [Amplia](https://docs.lacunasoftware.com/articles/amplia) têm número de série aleatório. Por padrão, os certificados de AC têm número de
série sequencial, mas, se você tiver uma [instância on-premises](https://docs.lacunasoftware.com/articles/amplia/on-premises), pode configurá-la para emitir também os certificados de AC
com números de série aleatórios (veja as instruções em [configurar os templates de certificado de AC](https://docs.lacunasoftware.com/articles/amplia/on-premises/configure-ca-cert-templates)).
## Tamanho e entropia
Os números de série aleatórios gerados pelo Amplia têm de 16 a 17 bytes, gerados com um gerador de números pseudoaleatórios
criptograficamente seguro (CSPRNG), com 122 bits de entropia forte.
Exemplos:
```text
0x33465bdd6636854a842dbee1085c352e
0x00cca41bb584e3c94cb32779d845ec0642
```
# [Histórico de versões do Amplia](https://docs.lacunasoftware.com/articles/amplia/changelog)
## 5.0.1 (2026-07-28)
Atualiza modelo do banco de dados: não
### Correções de bugs
AMP-393 Problemas com sessões de API que expiram em mais de 2^31 segundos (cerca de 25 dias)
## 5.0.0 (2026-07-14)
**Veja [Atualizando o Amplia da versão 4.x para 5.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-50)**
Idêntica a: 5.0.0 RC 3 (2026-07-03)
Atualiza modelo do banco de dados: **sim**
### Melhorias
AMP-370 Atualizar ASP.NET Core para versão 10.0 e o dashboard para Angular 21
AMP-391 Migrar o key store Azure Key Vault para a versão 4 do Azure SDK e o Azure.Identity, com a nova configuração `UseManagedIdentity`
AMP-392 Remover o suporte a métricas do Prometheus
## 4.22.0 (2026-07-02)
Idêntica a: 4.22.0 RC 1 (2026-04-16)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-390 Configuração para emitir certificados de AC com número de série aleatório
## 4.21.0 (2026-04-16)
Idêntica a: 4.21.0 RC 2 (2026-01-27)
Atualiza modelo do banco de dados: não
### Novas funcionalidades
AMP-386 Implementar encriptação e decriptação AES no key store Kryptus
### Melhorias
AMP-387 Permitir inclusão de dados de CEI INSS e número PIS/PASEP em certificados ICP-Brasil
### Correções de bugs
AMP-388 Erro "Cannot issue certificate with a start date prior to the CA certificate validity period" ao emitir certificado com AC recém-criada
AMP-389 API de geração de chaves no processo de emissão de *managed certificate* resulta em erro caso o nome do titular contenha vírgulas
## 4.20.0 (2026-01-14)
Idêntica a: 4.20.0 RC 1 (2026-01-07)
Atualiza modelo do banco de dados: **sim**
### Melhorias
AMP-384 Melhorias no key store PKCS #11 relevantes para ambientes com alta concorrência e melhorias na API de cálculo de IDN
### Correções de bugs
AMP-385 Evento de IdnComputed está salvando IDN raw
## 4.19.0 (2026-01-07)
Idêntica a: 4.19.0 RC 2 (2025-11-17)
Atualiza modelo do banco de dados: não
### Correções de bugs
AMP-355 Certificados podem ser emitidos com início da validade anterior à do emissor
## 4.18.0 (2025-11-12)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-378 Tratar key store partitions bloqueadas antes da hora
## 4.17.1 (2025-11-05)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-377 Comando `check-key-store-partitions-lock` para verificar estado de bloqueio das partições
## 4.17.0 (2025-11-05)
Idêntica a: 4.17.0 RC 4 (2025-09-29)
Atualiza modelo do banco de dados: **sim**
### Melhorias
AMP-243 Apagar chaves de processos incompletos de emissão de *managed certificate*
AMP-376 Adicionar número do pedido na confirmação de remoção das partições
## 4.16.0 (2025-09-19)
Atualiza modelo do banco de dados: não
### Novas funcionalidades
AMP-374 Comando `repair-blobs` para corrigir blobs corrompidos
## 4.15.1 (2025-09-08)
Idêntica a: 4.15.1 RC 4 (2025-09-03)
Atualiza modelo do banco de dados: não
### Correções de bugs
AMP-373 Erros no comando `check-blobs`
## 4.15.0 (2025-09-01)
Atualiza modelo do banco de dados: não
### Novas funcionalidades
AMP-328 Tela de criação de pedido de Carteira de Identificação Estudantil \(CIE\)
AMP-372 Comando `check-blobs` para verificar integridade dos blobs
## 4.14.0 (2025-08-12)
Idêntica a: 4.14.0 RC 2 (2025-08-12)
Atualiza modelo do banco de dados: não
### Novas funcionalidades
AMP-365 Assinatura pendente após emissão de Managed Certificate
### Melhorias
AMP-366 Alterações ao certificado SSL/TLS para adequação às regras do CA/B Forum
AMP-369 Configuração da validação de telefones
### Correções de bugs
AMP-368 Telefones com prefixo `+1 (305)` são recusados como não sendo de celular
## 4.13.0 (2025-07-23)
Idêntica a: 4.13.0 RC 4 (2025-05-30)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-363 Permitir envio de código de verificação por WhatsApp
AMP-364 Tornar validação de telefones mais flexível
AMP-367 Suporte \(experimental\) a integração com Prometheus
## 4.12.0 (2025-07-23)
Idêntica a: 4.12.0 RC 2 (2025-02-03)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-325 Melhorar e uniformizar formulários de Distinguished Name
AMP-360 Adequações para o certificado de sello electronico do certificado PY
## 4.11.0 (2025-01-22)
Idêntica a: 4.11.0 RC 2 (2024-11-06)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-359 Criar url argument `absoluteUrl` na API de obter link do termo assinado
AMP-361 Melhorias à integração com provedores de envio de SMS
## 4.10.0 (2024-10-24)
Idêntica a: 4.10.0 RC 4 (2024-09-13)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-318 Permitir criação de pedido de certificado de PJ por tela
AMP-356 Ajustes nos campos Unique Identifier e Organization Identifier de certificados CV
## 4.9.0 (2024-08-13)
Idêntica a: 4.9.0 RC 11 (2024-05-10)
Atualiza modelo do banco de dados: **sim**
### Novas funcionalidades
AMP-340 Health check com verificação de disponibilidade uma chave
AMP-342 Auto-verificação de LCRs
AMP-347 Imagem de Docker com driver PKCS #11 Kryptus
AMP-352 Key store clusters
### Melhorias
AMP-335 Atualizar framework SPA para 2.8
AMP-345 Permitir cadastro de cadeias em ACs independentemente do *CertificateChainType*
AMP-346 Evoluir client lib para permitir download do certificado da AC e da cadeia de certificação
### Correções de bugs
AMP-237 Erro relacionado a fuso horário na geração de termo de titularidade em Linux
AMP-343 Erros ao utilizar key store PKCS #11 em paralelo
AMP-351 Erro "Could not place a lock on the resource 'hangfire:lock:recurring-job:CleanupUsedOtps'" ao iniciar o sistema usando PostgreSQL
## 4.8.1 (2024-04-30)
Atualiza modelo do banco de dados: não
### Correções de bugs
AMP-350 Erro "Cannot write DateTime with Kind=UTC..." ao criar pedido de certificado para usuário usando PostgreSQL
## 4.8.0 (2024-02-27)
Idêntica a: 4.8.0 RC 5 (2024-02-27)
Atualiza modelo do banco de dados: **sim**
### Novas funcionalidades
AMP-332 API para cálculo de IDN (protocolo brasileiro de provimento de serviços biométricos)
AMP-333 Suporte a chaves AES (apenas em keystores PKCS #11 e database)
### Melhorias
AMP-245 Melhorar tratamento da validade de sessões de uso de managed certificates
AMP-337 Permitir configuração do label do token em key stores tipo PKCS #11
### Correções de bugs
AMP-341 Integer overflow ao setar um `CertificateManagement:OperationSessionTimeout` alto \(25+ dias\)
## 4.7.0 (2023-12-20)
Idêntica a: 4.7.0 RC 2 (2023-11-01)
Atualiza modelo do banco de dados: não
### Novas funcionalidades
AMP-334 Nova API para emissão de certificado em software que retorna chave em múltiplos formatos
### Melhorias
AMP-331 Melhorar integração com Amplia Reg para casos de certificados organizacionais
## 4.6.1 (2023-09-06)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-326 Melhorias na integração com HSMs Dinamo
### Correções de bugs
AMP-322 Caracteres proibidos pela ICP-Brasil não são automaticamente removidos de certificados BR
AMP-324 Erro *JsonReaderException: Bad JSON escape sequence*
## 4.6.0 (2023-08-30)
Atualiza modelo do banco de dados: não
### Novas funcionalidades
AMP-315 Habilitar acesso via SSH na imagem de Docker
AMP-320 API para decriptar com chave
AMP-321 Importação de chave PKCS #8 \(por ora suportado apenas em keystore em banco de dados\)
### Melhorias
AMP-327 Suporte a variável de ambiente STANDBY \(para instalação em Azure App Service\)
## 4.5.4 (2023-08-04)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-311 Atualizar pacote de envio de SMS para permitir configurar um `MessagingServiceSid` para provedor Twilio
## 4.5.3 (2023-08-04)
Atualiza modelo do banco de dados: não†
*† Esta atualização não introduz novas mudanças ao banco de dados mas muda, sim, a forma como alterações antigas são aplicadas*
### Correções de bugs
AMP-319 Algumas migrations passadas não podem ser aplicadas em SQL Server pré-2017 devido ao uso do `TRIM()`
## 4.5.2 (2023-07-05)
Atualiza modelo do banco de dados: não
### Correções de bugs
AMP-310 Consulta de horário OTP não está sendo repassada a key stores Dinamo (introduzido na [v4.4.0](https://docs.lacunasoftware.com/articles/amplia/changelog#v4-4-0))
## 4.5.1 (2023-06-29)
Atualiza modelo do banco de dados: não
### Correções de bugs
AMP-309 Campos com string vazia no SubjectName causam erro na emissão do certificado \(certificados SSL e Custom\)
## 4.5.0 (2023-06-14)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-306 Campo `ExtendedKeyUsageCritical` em certificados personalizados
## 4.4.0 (2023-06-02)
Atualiza modelo do banco de dados: **sim**
### Novas funcionalidades
AMP-279 Listagem e detalhes de managed certificates
AMP-295 Suporte a múltiplas políticas nos modelos de certificado
AMP-299 Configuração de servidores NTP com sincronização periódica e monitoramento do offset
### Melhorias
AMP-253 Configuração de AC para emitir certificados com link no AIA para arquivo .p7b ao invés de .cer
AMP-303 Configuração de modelo de certificado de AC
AMP-304 Permitir especificar o algoritmo de hash a ser utilizado para emitir o certificado no momento da criação do pedido
AMP-305 Adicionar traduções para o metadado de número do RG vindo do PMCert
## 4.3.0 (2023-05-19)
Atualiza modelo do banco de dados: **sim**
### Novas funcionalidades
AMP-293 Criar botão nos detalhes do pedido para emitir com uma CSR
AMP-294 Incluir campo CustomCertificateParameters.IncludeSubjectKeyId
AMP-297 Permitir personalização da página inicial
AMP-301 Suporte a configuração de webhook para notificação quanto um certificado é emitido
### Melhorias
AMP-296 Suporte a armazenamento de blobs em banco de dados
## 4.2.0 (2023-05-19)
Atualiza modelo do banco de dados: **sim**
### Novas funcionalidades
AMP-290 Criar comando para propagar CA Certs
### Melhorias
AMP-278 Melhorar tratamento de erro ao tentar copiar ID de CA/Template
AMP-292 Pemitir forçar algoritmo de assinatura de certificados por AC
### Correções de bugs
AMP-291 APIs de emissão de certificado em nuvem não permitem que o Amplia fique em rede privada
## 4.1.0 (2023-03-02)
Atualiza modelo do banco de dados: **sim**
### Novas funcionalidades
AMP-283 Emissão de certificado sem CSR, retornando arquivo PFX / PKCS #12
### Melhorias
AMP-273 Adicionado suporte a PostgreSQL
AMP-281 Permitir blob storage em "S3 clones"
AMP-284 Campos RetentionPeriodYears e LegislationCountries no certificado SSL
AMP-285 Aceitar IPv4 e IPv6 no `DnsNames` do certificado SSL
AMP-287 Refatorar como certificados de fatura eletrônica são emitidos (CV)
AMP-288 Codificar cédula de identidade no campo Unique Identifier para certificados de assinatura digital de CV
## 4.0.4 (2023-02-03)
Atualiza modelo do banco de dados: não
### Correções de bugs
AMP-286 Extensão ExtendedKeyUsage em certificados PY não está marcada como crítica
## 4.0.3 (2023-01-09)
Atualiza modelo do banco de dados: não
### Correções de bugs
AMP-280 Erro ao gerar QR Code para emissão em Docker
## 4.0.2 (2023-01-09)
Atualiza modelo do banco de dados: não
### Melhorias:
AMP-277 Mostrar ID nos detalhes da CA e do template
## 4.0.1 (2022-12-30)
Atualiza modelo do banco de dados: não
### Melhorias
AMP-276 Melhorar integração com HSM Dinamo
## 4.0.0 (2022-12-27)
**Veja [Atualizando o Amplia da versão 3.x para 4.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-40)**
Atualiza modelo do banco de dados: **sim** (veja [aviso](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-40))
### Novas funcionalidades
AMP-220 Novas APIs para suportar o [Lacuna PSC](https://docs.lacunasoftware.com/articles/psc)
AMP-268 Certificados de Cabo Verde
AMP-252 Novos perfis de certificado do Paraguay
AMP-249 Integração proprietária com [HSM Kryptus](https://docs.lacunasoftware.com/articles/amplia/on-premises/key-stores/kryptus)
AMP-271 Criar configuração de domínios de OCSP
### Melhorias
AMP-272 Atualizar ASP.NET Core para versão 6.0
AMP-258 Suporte a arquivo de configuração .conf/.ini
AMP-261 Criar índices faltando sobre pedidos e certificados
AMP-265 Campo SerialNumber na emissão de CSR personalizada
AMP-267 Criação de pedido com número de série pré-determinado
AMP-270 Suporte a múltiplas políticas de certificação
AMP-274 Gerar GUIDs "semi sequenciais" para melhorar desempenho do banco de dados
AMP-275 Criar configuração própria para domínios de OCSP
### Correções de bugs
AMP-269 Última CRL não é encontrada caso o nome da AC tenha letras em caixa alta
## 3.15.2 (2022-10-10)
Melhorias:
* [AMP-262] - Melhorar desempenho da emissão de LCRs em situações com muitas ACs (1000+)
Correções de bugs:
* [AMP-263] - Comunicação com Azure Key Vault não reutiliza conexões
Atualiza modelo do banco de dados: não
## 3.15.1 (2022-09-23)
Melhorias:
* [AMP-254] - Otimizar gerenciamento de filas de jobs
* [AMP-255] - Otimizar rotas de acesso a CRLs e CA Issuers
* [AMP-257] - Provedor de envio de SMS "EmailDump"
Atualiza modelo do banco de dados: não
## 3.15.0 (2022-06-13)
Novas funcionalidades:
* [AMP-239] - Certificado de Relações Trabalhistas (lei 14063/2020)
Melhorias:
* [AMP-240] - Opções nas APIs de certificado ICP-Brasil para normalizar o *Common Name* (CN=) e adicionar o sufixo com CPF/CNPJ
Atualiza modelo do banco de dados: não
## 3.14.0 (2022-04-29)
Novas funcionalidades:
* [AMP-232] - Certificado PKI Paraguay
* [AMP-112] - Implementar criação "genérica" de pedidos
* [AMP-218] - Integração via PKCS #11 com HSM Kryptus
* [AMP-233] - Permitir localização de chaves em key stores PKCS #11 pelo label
* [AMP-226] - Interface para geração de CSR com especificação de Subject Name
* [AMP-227] - Interface para registro de chave existente
* [AMP-225] / [AMP-216] - Permitir criar certificados pendentes \(com data de validade futura\)
* [AMP-228] - Propagação de LCRs e certificados de AC
Melhorias:
* [AMP-229] - Exibir datas de início e fim da validade nos detalhes do certificado
* [AMP-231] - Localização \(l10n\) para Espanhol \(en\)
Atualiza modelo do banco de dados: **sim**
## 3.13.2 (2021-12-16)
Correções de bugs:
* [AMP-224] - Chaves armazenadas em banco de dados não funcionam após migração entre servidores
Updates database model: no
## 3.13.1 (2021-12-05)
Novas funcionalidades:
* [AMP-207] - Adicionado suporte a Key Store com Google KMS
* [AMP-222] - Adicionado suporte a Key Store com Amazon KMS
* [AMP-208] - Adicionado suporte a OCSP
* [AMP-210] - Adicionado template de certificado Latinus (AC corporativa do Equador)
Melhorias:
* [AMP-223] - Adicionado margem segura de tempo no início de validade dos certificados de entidade final
Atualiza modelo do banco de dados: não
## 3.12.0 (2021-04-27)
Novas funcionalidades:
* [AMP-206] - Adicionado suporte a AWS S3 para armazenamento de arquivos
Atualiza modelo do banco de dados: **sim**
## 3.11.0 (2021-03-25)
Melhorias:
* [AMP-204] - Configuração `Amplia__IssueProcedureConfirmationEnabled` para desabilitar verificação de telefone celular na emissão
* [AMP-205] - Link de emissão direto para idioma correspondente à chamada de API
Atualiza modelo do banco de dados: não
## 3.10.2 (2021-03-21)
Melhorias:
* [AMP-201] - Adicionado suporte a provedores de SMS Zenvia e SmsEmpresa
Correções de bugs:
* [AMP-203] - Erro ao gerar QR Code de emissão de certificado
Atualiza modelo do banco de dados: não
## 3.10.1 (2021-03-15)
Correções de bugs:
* [AMP-200] - Erro ao emitir certificado de usuário-final em dispositivo criptográfico
Atualiza modelo do banco de dados: não
## 3.10.0 (2021-02-08)
Novas funcionalidades:
* [AMP-195] - Remoção (opcional) de chaves no Azure Key Store
Melhorias:
* [AMP-196] - Permitir remover chaves de CAs revogadas
Correções de bugs:
* [AMP-198] - Durante a revogação de certificado de CA, a CA é removida ao invés de ser desativada
* [AMP-197] - Erro nas mensagens no dialog de confirmação de ativação de CA
Atualiza modelo do banco de dados: não
## 3.9.6 (2020-11-26)
Melhorias:
* [AMP-184] - Adicionar configuração para esconder informações de "powered by"
* [AMP-187] - Permitir que a API de criação de pedido para usuário seja chamada para um usuário existente sem preencher o campo `subject`
Correções de bugs:
* [AMP-183] - Erro ao cadastrar pedido porque o email não está normalizado
* [AMP-188] - Dependendo do tamanho da tela, não é possível marcar a checkbox na assinatura do termo
* [AMP-189] - Erros em templates do tipo `EcuadorBce`
* [AMP-190] - Após remover uma chave que era de uma CA, erro ao listar CA Certificates
* [AMP-191] - Data de nascimento é pré-preenchida de maneira incorreta na criação de pedido para usuário
Atualiza modelo do banco de dados: não
## 3.9.5 (2020-10-20)
Correção de bug:
* [AMP-182] - Dependendo do relógio da máquina do usuário, o processo de emissão imediatamente acusa estar expirado
Atualiza modelo do banco de dados: não
## 3.9.4 (2020-10-08)
Correção de bug:
* [AMP-186] - Erros recorrentes na emissão de LCR
Atualiza modelo do banco de dados: não
## 3.9.3 (2020-09-30)
Correção de bug:
* [AMP-185] - Erro no envio de SMS com provedor TotalVoice
Atualiza modelo do banco de dados: não
## 3.9.2 (2020-09-03)
Melhorias:
* [AMP-181] - Adicionados novos temas
* *Metallic seaweed & Emerald* (`mse`)
* *Oxford blue & Green* (`obg`)
* *Castleton green & Orange* (`cgo`)
* *Viridian green & Yellow* (`vgy`)
Correções de bugs:
* [AMP-179] - QR Code de emissão não funciona quando o `QRCodeGatewayUrl` não está configurado
* [AMP-180] - Página de chaves de aplicação não abre
Atualiza modelo do banco de dados: não
## 3.9.1 (2020-08-05)
* Correção de bug: [AMP-178] - Tela fica em branco após login no Internet Explorer
Atualiza modelo do banco de dados: não
## 3.9.0 (2020-08-04)
Novas funcionalidades:
* [AMP-154] - Suporte a emissão de certificados conforme norma do Banco Central do Equador
Melhorias:
* [AMP-168] - Melhorar mecanismo de emissão de LCRs de modo a otimizar casos com centenas ou milhares de ACs
* [AMP-161] - Atualizar Angular para versão 9
Atualiza modelo do banco de dados: **sim**
## 3.8.4 (2020-07-22)
* [AMP-177] - Adicionar tema *Dark cerulean & Green* (`dcg`)
Atualiza modelo do banco de dados: não
## 3.8.3 (2020-07-21)
* [AMP-173] - Não gerar preemptivamente o arquivo na API de obter link para download do termo de titularidade
* Correção de bug: [AMP-174] - Tela de emissão fica em "loading" indefinidamente
Atualiza modelo do banco de dados: não
## 3.8.2 (2020-07-10)
* Correções de bugs
* [AMP-175] - Logs de aplicação de nível *Information* estão sendo escritos para o log de sistema por padrão no Linux e em Docker
* [AMP-176] - Nome do arquivo do termo de titularidade assinado não está internacionalizado
Atualiza modelo do banco de dados: não
## 3.8.1 (2020-06-24)
* Correção de bug: Ao aceitar a oferta para emitir o certificado no celular, o QR Code não aparece
Atualiza modelo do banco de dados: não
## 3.8.0 (2020-06-24)
* Implementado *soft delete* de pedidos de certificado
* Adicionada API para atualizar pedidos de certificado
* Melhorada experiência ao acessar pelo PC a página de emissão de um pedido de certificado que só pode ser emitido no celular
* Correção de bug: Sistema está permitindo remoção de pedido com certificado já emitido
Atualiza modelo do banco de dados: **sim**
## 3.7.5 (2020-06-23)
* Otimizado armazenamento de histórico de execuções de rotinas em background
* Adicionado argumento `--heavy` ao comando [update-db](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/update-db)
Atualiza modelo do banco de dados: não
## 3.7.4 (2020-06-19)
* Correções de bugs
* Provider de logs para o event log está causando erros em Azure App Service
* Um excesso de rotinas de emissão de LCR "encavaladas" poderia exaurir as conexões com o banco de dados disponíveis
Atualiza modelo do banco de dados: não
## 3.7.3 (2020-06-05)
* Adicionado tema *Green & Dark coral* (`gdc`)
* Correção de bug
* Campo `SmsEnabled` da API de *client config* é preenchido de maneira errada em algumas situações
Atualiza modelo do banco de dados: não
## 3.7.2 (2020-06-03)
* Remover acentos de mensagens de SMS
Atualiza modelo do banco de dados: não
## 3.7.1 (2020-05-21)
* Melhorados *labels* de telefone e "pedido para usuário (HSM em nuvem)"
Atualiza modelo do banco de dados: não
## 3.7.0 (2020-05-06)
* Adicionado suporte a proteção de chaves com PIN em HSMs Dinamo
* Adicionado filtro por `keyMedia` na API para obter pedidos de certificado pendentes para um usuário
* Adicionados temas *eva*, *dir* e *cam*
Atualiza modelo do banco de dados: não
## 3.6.0 (2020-05-01)
* Adicionada integração com HSMs Dinamo via comunicação proprietária
Atualiza modelo do banco de dados: não
## 3.5.0 (2020-04-14)
* Sistema migrado para ASP.NET Core 3.1 (versão LTS, suportada até dez/2022)
Veja [Atualizando o Amplia da versão 3.0-3.4 para 3.5](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-35)
Atualiza modelo do banco de dados: não
## 3.4.0 (2020-03-27)
* Adicionada possibilidade de assinar digitalmente o Termo de Titularidade contendo a chave pública do certificado durante o processo de emissão
* Adicionada data de nascimento ao formulário de criação de pedido ICP-Brasil
* Adicionado comando [gen-enc-key](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/gen-enc-key)
* Configuração `General.QRCodeGatewayUrl` passa a ser opcional
* Correções de bugs
* A logo não deve ser clicável na página de emissão de certificado
* Erro ao tentar editar template de certificado de atributo
* Texto inicial da tela de emissão de certificado não muda ao alterar o idioma
* QR Code de emissão não está sendo exibido ao clicar no botão *QR Code de emissão* nos detalhes do pedido
Atualiza modelo do banco de dados: **sim**
## 3.3.0 (2019-03-17)
* Adicionada possibilidade de configuração da extensão *certification policies* nos modelos de certificado
* Adicionado novo papel: *Agente de Registro* (pode apenas criar pedidos de certificado e revogar certificados)
* Suporte completo a temas (exceto personalização da página inicial)
* Adicionada possibilidade de ocultar botões de criação de pedido básico e associado a usuário
* Correções de bugs
* Em certas circunstâncias certificados podem ser emitidos com validade de 0 dias
* Erro ao criar pedido de certificado com usuário associado sem template
* Erro "You cannot specify the root subscription" ao entrar no sys admin
* Erro na logo do convite
Atualiza modelo do banco de dados: **sim**
## 3.2.0 (2019-03-10)
* Dashboard internacionalizado (português e inglês)
* Adicionado suporte a armazenamento de chaves de usuários finais protegidas por PIN ("HSM cloud")
* Adicionado suporte à criação de pedidos de certificado associados a contas de usuários
* Nome da AC passa a ser sugerido a partir do nome da chave
* Campo unidade organizacional (OU) não precisa mais ser informado durante a emissão de certificado de AC e criação de AC raiz
* Adicionado suporte parcial a temas (por ora apenas os emails são afetados)
* Adicionado comando [test-email](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/test-email)
* Correções de bugs
* Campo *validade* era exibido durante a criação de um pedido de certificado mesmo quando o formato selecionado não permite validade diferenciada
* Erros de inicialização não eram acusados no dashboard
Atualiza modelo do banco de dados: **sim**
## 3.1.0 (2019-02-22)
* Integração com servidor de OpenID Connect passa a ser opcional
* Adicionado [utilitário de linha de comando](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool) com:
* Comando [update-db](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/update-db) para atualizar manualmente o modelo do banco de dados
* Comando [hash-root-pass](https://docs.lacunasoftware.com/articles/amplia/on-premises/tool/hash-root-pass) para calcular o hash de uma senha de *root*
* Conta padrão agora é criada na primeira execução do sistema
* Corrigido bug que fazia com que configurações feitas no arquivo de configuração JSON tivessem precedência sobre variáveis de ambiente (que deveriam ter precedência máxima)
Atualiza modelo do banco de dados: não
## 3.0.0 (2019-02-06)
* Dashboard completamente refatorado
* Adicionado suporte à realização de operações no dashboard que antes só podiam ser feitas via API:
* Gerenciamento de templates de certificados
* Criação de ACs para emissão de certificados de atributo
* Adicionado [suporte a Docker](https://docs.lacunasoftware.com/articles/amplia/on-premises/docker)
* Alterado comportamento em Linux para logar para o *systemd journal* ([clique aqui para mais detalhes](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/troubleshoot/check-logs))
* Melhoria ao formato dos certificados de chave pública de usuários finais, adicionando extensão *AuthorityKeyIdentifier*
Atualiza modelo do banco de dados: **sim**
Mudanças de configuração: veja [Atualizando o Amplia da versão 2.16 para 3.0](https://docs.lacunasoftware.com/articles/amplia/on-premises/update-30)
## 2.16.1 (2019-12-12)
* Adicionada opção de configurar um *alias* para o tipo de entidade em templates de certificado do tipo *PrivateID*
Atualiza modelo do banco de dados: não
## 2.16.0 (2019-12-10)
* Adicionado certificado de atributo tipo *PrivateID* (para carteiras de identidade de entidades de classe)
Atualiza modelo do banco de dados: não
## 2.15.2 (2019-12-04)
* Alterado modelo de configuração para Linux para logar para a pasta */var/log/amplia*
Atualiza modelo do banco de dados: não
## 2.15.1 (2019-09-12)
* Melhorias ao certificado de chave pública tipo *Cnb* (funcionalidade específica para um cliente)
Atualiza modelo do banco de dados: não
## 2.15.0 (2019-08-10)
* Adicionado suporte a [instalação em Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux)
* Adicionado suporte a armazenamento de chaves de AC no banco de dados
* Adicionado suporte a armazenamento de arquivos no sistema de arquivos local
* Adicionado suporte ao provedor de SMS [TotalVoice](https://www.totalvoice.com.br/)
* Adicionada informação de tipo de certificado (A1/A3) em certificados ICP-Brasil
* Melhorias à página de criação de pedido de certificado
* Melhoria de escalabilidade: chaves de *data protection* agora são armazenadas no banco de dados
* Melhoria de segurança: chaves de *data protection* podem ser cifradas (obrigatório para utilizar armazenamento de chaves de AC em banco de dados)
* Correções de bugs
* Corrigida condição de corrida que permitida que dois certificados fossem emitidos para um mesmo pedido
* Corrigido erro de validação que permitia que um pedido fosse criado sem especificar data de validade com um template sem validade padrão
Atualiza modelo do banco de dados: **sim**
Mudanças de configuração:
* `General:EncryptionKey`: opcional (para *backward compatibility*), mas altamente recomendado ser preenchido com uma chave gerada aleatoriamente
(veja instruções para geração da chave em [Windows](https://docs.lacunasoftware.com/articles/amplia/on-premises/windows/install#encryption-key-generation) ou em [Linux](https://docs.lacunasoftware.com/articles/amplia/on-premises/linux/install-ubuntu#encryption-key-generation))
## 2.14.1 (2019-06-13)
* Correções de bugs
* Pesquisa textual nos certificados e pedidos não estava funcionando com termos parciais
* Corrigido erro ao tentar revogar certificado de atributo
Atualiza modelo do banco de dados: não
Mudanças de configuração: nenhuma
## 2.14.0 (2019-06-11)
* Interface para ver e desbloquear pedidos bloqueados
* Tela de criação do pedido adaptada para solicitar CA, Template e telefone celular
* Edição de pedidos desabilitada temporariamente
Atualiza modelo do banco de dados: não
Mudanças de configuração: nenhuma
## 2.13.0 (2019-05-09)
* Adicionada possiblidade de configurar e-mails enviados pelo sistema (tema e imagens)
* Criada API para envio de SMS com link para emissão
* Adicionado campo `KeyMediaConstraints` no `OrderModel`
Atualiza modelo do banco de dados: não
Mudanças de configuração:
* `General:Theme` (opcional)
* Valores válidos: vazio (padrão) ou `Teal`
## 2.12.1 (2019-04-18)
* PKI SDK atualizado para versão [2.4.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-4-0)
* Correções de bugs
* Certificados SSL não permitem uso de wildcard
* "Encavalamento" de jobs de emissão de LCR quando a emissão fica impossibilitada por muito tempo
Atualiza modelo do banco de dados: não
Mudanças de configuração: nenhuma
## 2.12.0 (2019-03-21)
* Emissão de certificado em celular
Atualiza modelo do banco de dados: não
Mudanças de configuração
* `General:SmsContextInfo` (opcional)
* Permite configurar uma string a ser utilizada como informação de "contexto" nos SMSs enviados pelo sistema, por exemplo: "Link para emissão do seu certificado *na plataforma X*: ..." (nesse exemplo, o valor da configuração seria `na plataforma X`)
* Configurações opcionais para uso de aplicativo mobile personalizado (padrão todas configurações vazias)
* `PkiSuite:MobileAppName`
* `PkiSuite:MobileAppCodeSuffix`
* `PkiSuite:MobileIOSStoreUri`
* `PkiSuite:MobileAndroidStoreUri`
## 2.11.1 (2019-02-26)
* Adicionada validação de endereço de e-mail na criação de pedidos de certificados tipo ICP-Brasil (`PkiBrazil`)
* PKI SDK atualizado para versão [2.3.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-3-0) (RTM)
Atualiza modelo do banco de dados: não
Mudanças de configuração: nenhuma
## 2.11.0 (2019-02-15)
* Adicionado suporte a certificados para blockchain [IBM Hyperledger](https://www.ibm.com/blockchain/hyperledger)
* Adicionado suporte a curvas elípticas (ECC)
* Emissão de certificados com chave ECC
* Armazenamento de chaves ECC em Azure Key Vault -- curvas suportadas:
* P-256 (secp256r1)
* P-256K (secp256k1)
* P-384 (secp384r1)
* P-521 (secp521r1)
* PKI SDK atualizado para versão [2.3.0](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-3-0) (beta 3)
Atualiza modelo do banco de dados: não
Mudanças de configuração: nenhuma
## 2.10.3 (2019-01-21)
* Melhorias à configuração de envio de SMS
* PKI SDK atualizado para versão [2.2.6](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/changelog#v2-2-6)
Atualiza modelo do banco de dados: não
Mudanças de configuração
* `SMS:Type` (opcional)
* Configura o provedor de envio de SMS.
* Valores válidos: vazio (padrão) ou `Twilio` (futuramente será adicionado suporte a outros provedores)
* Caso a configuração seja omitida, é assumido o valor `Twilio` (para *backward compatibility*)
* Novas configurações do Twilio: pode-se especificar `SMS:AccountSid` e `SMS:AuthToken` ao invés de especificar `SMS:TwilioBasicCredential`
* A antiga configuração `SMS:TwilioBasicCredential` ainda é suportada para fins de *backward compatibility*
## 2.10.2 (2019-01-17)
* ASP.NET Core atualizado para 2.2
* Novo arquivo de configuração para hospedagem em IIS (appsettings.iis.json)
* Correção na codificação do cargo no certificado tipo 6
Atualiza modelo do banco de dados: não
Mudanças de configuração: nenhuma
## 2.10.1 (2019-01-14)
* Corrigido bug que causava erro na emissão de certificado quando o Web PKI não estava instalado
Atualiza modelo do banco de dados: não
Mudanças de configuração: nenhuma
## 2.10.0 (2019-01-10)
* Primeira versão disponível para instalação *on-premises*
# [Soluções para certificação digital](https://docs.lacunasoftware.com/articles/pki-guide)
Temos uma linha completa de soluções para certificação digital:
* [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) - plugin para navegadores que possibilita acesso aos certificados digitais do usuário a partir de páginas web
* [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki) - serviço em nuvem para realizar assinaturas digitais e outras operações de certificação digital em virtualmente qualquer linguagem de programação
* [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk) - uma biblioteca para aplicações .NET para certificação digital
* [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) - solução para realizar assinaturas digitais e outras operações de certificação digital em virtualmente qualquer linguagem de programação e que pode ser hospedado em servidores Windows e Linux.
A escolha de qual(is) produto(s) se adequa(m) melhor ao seu projeto depende do seu cenário e do que você está tentando
implementar. Leia o artigo abaixo que mais se aproxime do seu caso para saber como melhor implementá-lo:
* **[Assinaturas web](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures)** - realizar [assinaturas digitais](https://docs.lacunasoftware.com/articles/pki-guide/signatures) em páginas web
* [Assinaturas de servidor](https://docs.lacunasoftware.com/articles/pki-guide/server-signatures) - realizar assinaturas digitais inteiramente no backend, com chaves localizadas no servidor
* [Login com certificado](https://docs.lacunasoftware.com/articles/pki-guide/cert-auth) - autenticação de usuários (login) por certificado digital
# [Assinaturas digitais](https://docs.lacunasoftware.com/articles/pki-guide/signatures)
Antes de definir o que é uma assinatura digital, precisamos falar de um conceito mais amplo, a **assinatura eletrônica**. A Wikipédia (em inglês) a define assim:
> Uma assinatura eletrônica, ou e-signature, refere-se a dados em formato eletrônico que estão logicamente associados a outros dados em formato eletrônico e que são usados pelo signatário para assinar.
Nesse sentido, uma assinatura eletrônica pode ser entendida como uma ampla gama de processos para associar um **signatário** a um **documento eletrônico** por meio de algum tipo de **compromisso** (por exemplo, o compromisso de cumprir o conteúdo do documento, ou o compromisso de ter testemunhado um acordo).
> **Observação**
>
> Em inglês, os termos *signatory* e *signer* são sinônimos (ambos correspondem a "signatário"); a versão em inglês desta documentação usa o segundo.
Por exemplo, a impressão de um documento que primeiro é assinado à mão e depois enviado por fax pode ser considerada uma assinatura eletrônica. No entanto, é possível argumentar que uma assinatura desse tipo pode ser facilmente forjada. Processos de assinatura eletrônica diferentes oferecem garantias (em inglês, *assurances*) diferentes.
Os processos de assinatura eletrônica podem ser classificados em diferentes **níveis de garantia**. O regulamento [eIDAS](https://www.eid.as/) define três níveis crescentes de garantia:
1. Assinaturas eletrônicas
1. Assinaturas eletrônicas avançadas
1. Assinaturas eletrônicas qualificadas
Para os fins desta documentação, uma **assinatura digital** é uma **implementação de assinatura eletrônica** que usa **certificados de chave pública X.509** e segue um **padrão de assinatura** específico para alcançar os **mais altos níveis de garantia**.
> **Observação**
>
> O termo *assinatura digital* não deve ser confundido com *algoritmo* de assinatura digital, que é um tipo de algoritmo criptográfico assimétrico (por exemplo, o algoritmo RSA). Em geral, os algoritmos de assinatura digital são uma pequena parte da implementação das assinaturas digitais.
Em todos os outros artigos, quando dizemos simplesmente *assinatura*, estamos nos referindo a uma *assinatura digital* conforme definida acima.
## Padrões de assinatura
Os principais padrões de assinatura digital em uso hoje são:
* [CMS Advanced Electronic Signatures (CAdES)]()
* [PDF Advanced Electronic Signatures (PAdES)](https://en.wikipedia.org/wiki/PAdES)
* [XML Advanced Electronic Signatures (XAdES)](https://www.w3.org/TR/XAdES/)
# [Assinaturas web](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures)
Uma *assinatura web* é uma [assinatura digital](https://docs.lacunasoftware.com/articles/pki-guide/signatures) realizada em uma página web utilizando um dos certificados digitais
do usuário.
Esse tipo de assinatura **necessariamente requer que sejam realizadas operações no frontend** porque a chave privada do certificado
do usuário pode estar em um dispositivo criptográfico (certificado A3) que nunca permite a sua exportação. Além disso, mesmo que a
chave seja baseada em software (certificado A1), solicitá-la ao usuário é uma má prática.
Suportamos duas formas de implementar assinaturas web:
* [Assinaturas web remotas](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/remote) - nessa abordagem, são feitas no frontend apenas as operações que necessariamente precisam
ser realizadas no lado do cliente, todas as outras operações são realizadas no backend. De certa forma, o backend realiza uma
assinatura com uma chave *remota* (localizada no lado do cliente)
* [Assinaturas web locais](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/local) - nessa abordagem, todas as operações são realizadas *localmente* no frontend, sem cooperação
do backend.
Em situações em que o arquivo a ser assinado já está no servidor e/ou o resultado da assinatura precisa estar no servidor ao final
do processo, **assinaturas remotas** possuem a grande vantagem de não requerirem que arquivos sejam trafegados entre o servidor e o
cliente. Apenas um tráfego de rede mínimo é realizado (da ordem de 3KB).
Apenas em cenários em que em hipótese alguma pode ocorrer upload de arquivos para o servidor é que **assinaturas locais** devem ser
empregadas. Mesmo assim, essa decisão deve ser tomada com cuidado, pois realizar uma assinatura digital é um processo complexo que
requer acesso de rede para obter diversos arquivos (por exemplo Listas de Certificados Revogados), o que pode falhar no ambiente
de alguns usuários. Além disso, atualmente só suportamos assinaturas locais no Windows, enquanto que suportamos assinaturas remotas
no Windows, Mac OS e Linux.
Portanto, **recomendamos escolher a abordagem de [assinaturas web remotas](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/remote) na maioria dos casos.**
# [Assinaturas web remotas](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/remote)
Uma *assinatura remota* é uma [assinatura digital](https://docs.lacunasoftware.com/articles/pki-guide/signatures) realizada com uma chave privada que não está disponível localmente,
ou seja, em que o algoritmo de assinatura em si precisa ser computado remotamente. Como a realização do algoritmo pode depender de
diversos fatores como latência de rede e intervenção do usuário, o processo de realizar assinaturas remotamente é inerentemente
assíncrono, significando que o processo se inicia, espera-se a realização remota do algoritmo e só então finaliza-se o processo.
Chamamos de *assinaturas web remotas* o caso especial de assinaturas remotas em que o processo de assinatura está sendo realizado numa
aplicação web com o backend controlando o processo, porém utilizando um certificado do usuário, cuja chave privada está disponível
somente no lado do cliente.
Na maioria dos casos **esta é a forma recomendada de implementar assinaturas web com nossos produtos** (veja o artigo
[assinaturas web](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures) para exceções).
Esse processo, em linhas gerais, ocorre da seguinte maneira:
1. O backend inicia a assinatura, gerando os bytes que precisam ser utilizados como entrada do algoritmo de assinatura utilizando
a chave privada do usuário (chamados de "to-sign-data")
1. O "to-sign-data" é transmitido para o lado do cliente
1. O algoritmo de assinatura é realizado no frontend com a chave privada do certificado do usuário
1. O resultado do algoritmo de assinatura é transmitido de volta ao servidor
1. O backend finaliza o processo de assinatura

> **Observação**
>
> Isso é apenas uma visão geral. Existem passos adicionais, como ler a codificação binária do certificado no frontend e enviar
> ao backend.
**A forma recomendada de implementar assinaturas web remotas é utilizando o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki)** em conjunto com
o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki).
## Passo-a-passo
1. Acesse a [documentação do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki)
1. Escolha uma linguagem de programação
1. Escolha um dos projetos de exemplos disponíveis
1. Siga os passos para executar o projeto
1. Uma vez executando o projeto, clique em algum dos exemplos de assinatura
> **Dica**
>
> Embora a solução envolva o Rest PKI em conjunto com o Web PKI (para as operações no backend e frontend respectivamente),
> **concentre-se na documentação do Rest PKI** que já mostra o uso dos dois produtos integrados.
## Implementações alternativas
Se a sua aplicação web for em .NET (C#, VB.NET, C++.NET), você pode também utilizar o [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk) no backend
em conjunto com o Web PKI no frontend. Para mais informações, veja o artigo
[Assinatura com chave remota](https://docs.lacunasoftware.com/articles/pki-sdk/signatures/web-remote) na documentação do SDK.
Se você já possui uma biblioteca de terceiros para certificação digital ou deseja implementar o código do backend você mesmo,
você pode também utilizar o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) apenas para realizar as operações necessárias no frontend.
# [Assinaturas web locais](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/local)
Uma *assinatura local* é uma [assinatura digital](https://docs.lacunasoftware.com/articles/pki-guide/signatures) realizada com uma chave privada disponível
localmente e que, portanto, pode ser feita de forma *síncrona* (ao contrário das assinaturas remotas, que precisam
ser realizadas de forma assíncrona). Por exemplo, uma assinatura digital realizada no backend com uma chave privada
disponível no lado do servidor é uma assinatura local (chamamos essas assinaturas de [assinaturas de servidor](https://docs.lacunasoftware.com/articles/pki-guide/server-signatures)).
Chamamos de *assinatura web local* o caso especial de assinatura local realizada em uma página web
com uma chave disponível no lado do cliente (ou seja, a chave de um dos certificados
do usuário).
Como explicado no artigo [assinaturas web](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures), essa forma de implementar assinaturas web deve
ser considerada com cuidado, pois realizar uma assinatura digital é um processo complexo que requer acesso de rede
para obter artefatos de revogação (Listas de Certificados Revogados), o que pode falhar no computador de alguns usuários.
**Por isso, na maioria dos casos recomendamos implementar assinaturas web com a abordagem de [assinaturas web remotas](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures/remote).**
No entanto, quando em hipótese alguma os arquivos podem ser enviados ao servidor, assinar localmente
no frontend pode ser a única opção. Nesses casos, use a **extensão de assinatura local** do
**[Web PKI](https://docs.lacunasoftware.com/articles/web-pki)**. [Fale conosco](https://www.lacunasoftware.com/en/home/purchase) para mais detalhes.
# [Assinaturas de servidor](https://docs.lacunasoftware.com/articles/pki-guide/server-signatures)
Uma *assinatura de servidor* é uma [assinatura digital](https://docs.lacunasoftware.com/articles/pki-guide/signatures) realizada com uma chave privada localizada no servidor,
que, portanto, pode ser feita inteiramente no backend (ao contrário das [assinaturas web](https://docs.lacunasoftware.com/articles/pki-guide/web-signatures), que
necessariamente envolvem pelo menos uma etapa realizada no frontend).
A forma de implementar assinaturas de servidor depende da sua linguagem de programação. Leia a seção correspondente abaixo:
## Aplicações .NET
**A forma recomendada de implementar assinaturas de servidor em aplicações .NET é usar o [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk).**
Ao importar o arquivo `.pfx` do certificado que será usado na assinatura para o repositório de certificados do sistema
operacional, o certificado fica disponível quando você carrega os certificados do repositório do sistema. Veja o artigo
[Certificados com chave privada associada](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/certs-with-key) para saber como carregar
certificados do repositório do sistema operacional, e o artigo [Assinaturas](https://docs.lacunasoftware.com/articles/pki-sdk/signatures) para saber como realizar
assinaturas.
## Outras linguagens de programação
**A forma recomendada de implementar assinaturas de servidor em todas as outras linguagens de programação é usar o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki).**
Os exemplos do Rest PKI que demonstram como fazer isso se chamam *PAdES server key* e *CAdES server key*.
Consulte a [disponibilidade de exemplos do Rest PKI por projeto](https://docs.lacunasoftware.com/articles/rest-pki/samples-availability) para ver em quais projetos de exemplos
esses exemplos estão disponíveis.
# [Login com certificado digital](https://docs.lacunasoftware.com/articles/pki-guide/cert-auth)
É possível realizar autenticação de usuários (login) baseada em certificado digital. Esse processo, em linhas gerais, ocorre da seguinte
maneira:
1. Gera-se um [nonce criptográfico](https://pt.wikipedia.org/wiki/Nonce)
1. Solicita-se ao usuário que realize um algoritmo de assinatura sobre o *nonce* utilizando sua chave privada
1. Verifica-se o resultado do algoritmo de assinatura utilizando a chave pública do certificado do usuário
1. Valida-se o certificado do usuário
1. Se todas as validações forem bem sucedidas, a aplicação pode assumir que o usuário é realmente o titular do certificado apresentado.
Em seguida, utiliza-se um campo chave do certificado (ex: CPF ou email) para localizar o usuário na base de dados e completa-se o
processo de autenticação.
> **Observação**
>
> Isso é apenas uma visão geral. Existem passos adicionais, como validar que o *nonce* nunca foi utilizado anteriormente, para
> evitar ataques de replay.
Para implementar esse processo, é preciso realizar operações no frontend e no backend.
O algoritmo de assinatura sobre o *nonce* necessariamente precisa ser feito no **frontend** pois a chave privada do certificado
do usuário pode estar em um dispositivo criptográfico (certificado A3) que nunca permite a sua exportação. Além disso, mesmo que a
chave seja baseada em software (certificado A1), solicitá-la ao usuário é uma má prática.
Já a geração do *nonce* e todas as validações precisam ser realizadas no **backend** por motivos óbvios de segurança.
**A forma recomendada de implementar login com certificado digital é utilizando o [Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki)** em conjunto
com o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki).
## Passo-a-passo
1. Acesse a [documentação do Rest PKI](https://docs.lacunasoftware.com/articles/rest-pki)
1. Escolha uma linguagem de programação
1. Escolha um dos projetos de exemplos disponíveis
1. Siga os passos para executar o projeto
1. Uma vez executando o projeto, clique em *Authentication with digital certificate*
> **Dica**
>
> Embora a solução envolva o Rest PKI em conjunto com o Web PKI (para as operações no backend e frontend respectivamente),
> **concentre-se na documentação do Rest PKI** que já mostra o uso dos dois produtos integrados.
## Implementações alternativas
Se a sua aplicação web for em .NET (C#, VB.NET, C++.NET), você pode também utilizar o [PKI SDK](https://docs.lacunasoftware.com/articles/pki-sdk) no backend
em conjunto com o Web PKI no frontend. Para mais informações, veja o artigo
[Autenticação com certificado](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/auth) na documentação do SDK.
Se você já possui uma biblioteca de terceiros para certificação digital ou deseja implementar o código do backend você mesmo,
você pode também utilizar o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki) apenas para realizar as operações necessárias no frontend.
# [Certificados de teste](https://docs.lacunasoftware.com/articles/pki-guide/test-certs)
Se você não tiver um certificado ICP-Brasil para testar nossos produtos, você pode
utilizar um dos certificados para testes da ICP de testes da Lacuna Software.
> **Aviso**
>
> A ICP de testes da Lacuna Software nunca deve ser aceita em ambiente de produção!
**[Baixar certificados para testes](https://docs.lacunasoftware.com/pt-br/content/test-certs.zip)**
Todos os arquivos no zip acima estão em formato PKCS #12 (arquivos .pfx) com senha **1234**. Os seguintes certificados
estão presentes:
* Alan Mathison Turing
* Email: testturing@lacunasoftware.com
* Mimetiza certificado ICP-Brasil de pessoa física com CPF 560.723.861-05
* Ferdinand Georg Frobenius
* Email: testfrobenius@lacunasoftware.com
* Mimetiza certificado ICP-Brasil de pessoa física com CPF 873.780.111-26
* Pierre de Fermat
* Email: test@lacunasoftware.com
* Mimetiza certificado ICP-Brasil de pessoa física com CPF 473.633.618-86
* Wayne Enterprises, Inc
* Email: test@wayneenterprises.com
* Mimetiza certificado ICP-Brasil de pessoa jurídica com CNPJ 34.785.515/0001-66
* Representante legal: Bruce Wayne, CPF 478.911.786-31
Se você precisar de um certificado diferente com dados específicos, você mesmo pode [emitir um certificado de testes](https://demos.lacunasoftware.com/pt/demos/generate-certificate) com os dados que quiser.
Lembre-se de sempre **remover a confiança na ICP de testes da Lacuna Software ao levar a sua aplicação para produção**. Ainda melhor,
recomendamos o emprego de alguma técnica de compilação condicional de modo que a ICP de testes seja aceita apenas quando o código
estiver executando em modo *debug*.
## O que os arquivos contêm
* Cada arquivo contém só o certificado final e a chave privada dele, sem a cadeia. Importar um arquivo não instala a raiz nem a AC intermediária de testes.
* Os certificados são emitidos pela **Lacuna CA Test v7**, que é emitida pela **Lacuna Root Test v3**. Para confiar na ICP de testes, confie nessa raiz:
* Subject: `C=BR, O=ICP-Brasil, OU=Lacuna Software - LS, CN=Lacuna Root Test v3`, válida de 16/12/2024 a 16/12/2074.
* Impressão digital SHA-256: `BE:1E:84:17:7B:80:F1:88:9A:65:F1:10:10:A8:FB:85:49:5B:E7:0F:FC:3D:83:F5:C6:D6:3A:92:BE:DC:C0:18`.
* Download: [lacuna-root-test-v3.cer](http://ca.lacunasoftware.com/certs/lacuna-root-test-v3.cer). A AC intermediária é a [lacuna-ca-test-v7.cer](http://ca.lacunasoftware.com/certs/lacuna-ca-test-v7.cer).
* Os certificados valem até janeiro de 2075, com os usos de chave Digital Signature, Non Repudiation e Key Encipherment, e os usos estendidos autenticação de cliente TLS e proteção de e-mail.
* Os arquivos usam a criptografia antiga do PKCS #12 (3DES para a chave, RC2 de 40 bits para o certificado, MAC com SHA-1). O OpenSSL 3 os recusa com o erro `unsupported ... RC2-40-CBC`, a menos que você passe `-legacy`:
```bash
openssl pkcs12 -legacy -in "Pierre de Fermat.pfx" -passin pass:1234 -nokeys
```
## Confiando na ICP de testes em cada produto
Cada produto decide à sua maneira em quais raízes confia, e você precisa configurar cada um para confiar na ICP de testes.
As seções abaixo mostram como, e o que fazem os [projetos de exemplo](https://github.com/LacunaSoftware/PkiSuiteSamples):
| Produto | Como confiar na ICP de testes | O que os exemplos fazem |
| --- | --- | --- |
| [Web PKI](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#web-pki) | Instale o certificado no repositório do usuário; a confiança vem do Rest PKI ou dos `trustArbitrators` de uma assinatura local | .NET: adiciona uma raiz de testes antiga, que não aceita mais estes certificados |
| [Rest PKI](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#configurando-o-rest-pki) | Contexto de segurança `803517ad-3bbc-4169-b085-60053a8f6dbf` | .NET: builds de debug; demais linguagens: `trustLacunaTestRoot` |
| [Rest PKI Core](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#rest-pki-core) | O mesmo contexto de segurança, por requisição ou como padrão da organização | .NET: builds de debug; Java: `trustLacunaTestRoot` |
| [PKI SDK](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#pki-sdk) | `TrustedRoots` com a Lacuna Root Test v3 num `LinkedTrustArbitrator` | .NET: builds de debug |
| [PKI Express](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#pki-express) | `trustLacunaTestRoot` no operador | `trustLacunaTestRoot` na configuração |
| [Signer](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#signer) | Adicione a raiz ao TrustArbitrator da instância (on-premises) | Assinam no sandbox com um certificado de teste |
## Web PKI
O Web PKI lista os certificados do repositório de certificados do sistema operacional e dos dispositivos criptográficos conectados, então um certificado de teste precisa estar instalado para que o Web PKI o liste.
**Windows.** Importe o arquivo `.pfx` para o repositório **Pessoal** do usuário atual. Dê um duplo clique no arquivo e siga o Assistente para Importação de Certificados (local do repositório **Usuário Atual**, senha `1234`), ou rode no PowerShell:
```powershell
Import-PfxCertificate -FilePath ".\Pierre de Fermat.pfx" -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString 1234 -AsPlainText -Force)
```
Depois disso, o `certmgr.msc` mostra o certificado na pasta **Pessoal**, com a indicação de que você tem a chave privada correspondente. Veja [Primeiros passos](https://docs.lacunasoftware.com/articles/web-pki/get-started).
TODO(docs): como instalar os arquivos PKCS #12 de teste para que o Web PKI os liste no Linux e no macOS. Nenhuma fonte diz qual repositório o Web PKI lê nesses sistemas.
**Confiança.** Instalar o certificado não faz uma assinatura confiar nele:
* Com o Rest PKI ou o Rest PKI Core, quem decide é o contexto de segurança da operação: veja [Configurando o Rest PKI](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#configurando-o-rest-pki) e [Rest PKI Core](https://docs.lacunasoftware.com/articles/pki-guide/test-certs#rest-pki-core).
* Numa assinatura feita só com o Web PKI, como a `signPdf()`, adicione a Lacuna Root Test v3 aos `trustArbitrators` como árbitro `trustedRoot`, com a raiz codificada em Base64, só em desenvolvimento. O exemplo em ASP.NET MVC faz isso em `Views/PadesSignatureWeb/Index.cshtml`, mas o `_lacunaTestRoot` dele (em `Views/Shared/_Layout.cshtml`) é uma raiz antiga, a Lacuna Root Test v1, que expirou em janeiro de 2025 e não é a raiz destes certificados: use a Lacuna Root Test v3.
```javascript
trustArbitrators: [
pki.standardTrustArbitrators.pkiBrazil,
// Lacuna Software Test Root (development only!)
{ type: 'trustedRoot', trustedRoot: lacunaTestRootBase64 }
]
```
## Configurando o Rest PKI
O ID do contexto de segurança que contém a ICP de testes da Lacuna Software é:
```text
Lacuna Test PKI security context ID (for development purposes only!!!):
803517ad-3bbc-4169-b085-60053a8f6dbf
```
Passe-o como o contexto de segurança da operação. Por exemplo, para iniciar uma assinatura PAdES (PDF) em PHP:
```php
// Trust Lacuna Test PKI (for development purposes only!!!)
$signatureStarter->setSecurityContext("803517ad-3bbc-4169-b085-60053a8f6dbf");
```
Ou em Java:
```java
// Trust Lacuna Test PKI (for development purposes only!!!)
signatureStarter.setSecurityContext(new SecurityContext("803517ad-3bbc-4169-b085-60053a8f6dbf"));
```
Nos projetos do [PkiSuiteSamples](https://github.com/LacunaSoftware/PkiSuiteSamples) você não altera o exemplo: todas as páginas obtêm o contexto de segurança de um auxiliar `Util`, que devolve o contexto da ICP de testes da Lacuna quando a chave do projeto está ligada.
| Linguagem | Exemplo (PAdES com o Rest PKI) | Chave |
| --- | --- | --- |
| PHP | `php/plain/public/pades-signature-rest/index.php`, pelo `Util::getSecurityContextId()` | `"trustLacunaTestRoot" => true` em `php/plain/config.php` (`true` por padrão) |
| Java | `java/springmvc/.../controller/PadesSignatureRestController.java`, pelo `Util.getSecurityContext()` | `trustLacunaTestRoot` em `application.yml` (`false`) e `application-dev.yml` (`true`) |
| .NET | `dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestController.cs`, pelo `Util.GetSecurityContextId()` | Build de debug: `StandardSecurityContexts.LacunaTest` sob `#if DEBUG`, `PkiBrazil` nos demais |
| Node.js | | `trustLacunaTestRoot` em `nodejs/expressmvc/config/*.js` |
| Python | | `TRUST_LACUNA_TEST_ROOT` em `python/flask/sample/config.py` e `python/django/sample/config.py` |
O exemplo em .NET avisa que uma instalação on-premises do Rest PKI pode não ter esse contexto de segurança; se a sua não tiver, entre em contato com o suporte ao desenvolvedor.
A partir daí, os certificados da nossa ICP de testes passam a ser aceitos.
## Rest PKI Core
Toda instância do Rest PKI Core tem o contexto de segurança padrão **Lacuna Test PKI (for development purposes only!)**, com o mesmo ID, `803517ad-3bbc-4169-b085-60053a8f6dbf` (`StandardSecurityContexts.LacunaTest` no cliente .NET). A instância o cria ao iniciar, então as instâncias on-premises também o têm. Ele confia na ICP-Brasil, no repositório de certificados confiáveis do Windows e na Lacuna Root Test v3.
Use-o de uma de duas formas:
* **Por requisição**: passe o ID como `SecurityContextId` ao criar uma sessão de assinatura (`CreateSignatureSessionRequest.SecurityContextId`) ou preparar uma assinatura (`PrepareSignatureOptions.SecurityContextId`, `securityContextId` no `POST api/signature`).
* **Para toda a organização**: no dashboard, escolha-o em **Configurações** > **Configurações gerais** > **Contexto de segurança padrão**, o contexto de segurança usado quando a requisição não informa um. Só usuários com o papel **Admin** ou **Manager** na organização veem **Configurações**.
Quando a requisição não informa um contexto de segurança, o Rest PKI Core usa o padrão da organização, ou então a configuração `General:DefaultSecurityContext` da instância, cujo padrão é a ICP-Brasil (PKI Brazil). Por isso, por padrão, os certificados de teste não são aceitos.
Nos exemplos, o projeto em ASP.NET MVC passa `StandardSecurityContexts.LacunaTest` em builds de debug e `PkiBrazil` nos demais; o projeto em Java passa `SecurityContext.lacunaTest` só quando `trustLacunaTestRoot` é `true` no `application.yml`, que vem com `false`.
TODO(docs): se o core.pki.rest oferece chaves de API de teste ou uma organização de avaliação. Os exemplos trazem uma chave de avaliação para o core.pki.rest que expira em 31/10/2026.
## PKI SDK
O PKI SDK não tem contexto de segurança: as raízes em que ele confia vêm de um `ITrustArbitrator`. Para confiar na ICP de testes, adicione a Lacuna Root Test v3 como `TrustedRoots` a um `LinkedTrustArbitrator`, só em builds de debug. Os exemplos fazem isso no `Util.GetTrustArbitrator()`:
```cs
var trustArbitrator = new LinkedTrustArbitrator(TrustArbitrators.PkiBrazil, TrustArbitrators.Windows);
#if DEBUG
// For development purposes, we also trust in Lacuna Software's test certificates.
// lacunaRootBytes: Lacuna Root Test v3 (the samples embed it in Base64)
var lacunaRoot = PKCertificate.Decode(lacunaRootBytes);
trustArbitrator.Add(new TrustedRoots(lacunaRoot));
#endif
```
Depois, use esse árbitro onde uma validação ou uma política de assinatura precisar de um:
* **PAdES**: passe-o para a política, por exemplo `PadesPoliciesForGeneration.GetPadesBasic(trustArbitrator)` e `PadesPoliciesForValidation.GetPadesBasic(trustArbitrator)`.
* **Políticas da ICP-Brasil** vêm com o próprio árbitro de confiança, que confia só nas raízes da ICP-Brasil, então recusam um certificado de teste mesmo que o resto da sua aplicação use o seu árbitro. Substitua-o, só em builds de debug:
* CAdES, como `CadesPoliciesForGeneration.GetPkiBrazilAdrBasica()`: `policy.ClearTrustArbitrator()` e depois `policy.AddTrustArbitrator(trustArbitrator)`.
* XML, como `BrazilXmlPolicySpec.GetNFePadraoNacional()`: `policy.ClearTrustArbitrators()` e depois `policy.AddTrustArbitrator(trustArbitrator)`.
Veja [Validando certificados](https://docs.lacunasoftware.com/articles/pki-sdk/certificates/validation) para `TrustedRoots` e `LinkedTrustArbitrator`.
## PKI Express
Ligue `trustLacunaTestRoot` no operador (`trust_lacuna_test_root` em Python e Ruby). Veja "Confiando em certificados que não sejam da ICP-Brasil" para [Node.js](https://docs.lacunasoftware.com/articles/pki-express/nodejs/trusting-in-custom-certificates), [Java](https://docs.lacunasoftware.com/articles/pki-express/java/trusting-in-custom-certificates), [PHP](https://docs.lacunasoftware.com/articles/pki-express/php/trusting-in-custom-certificates), [Python](https://docs.lacunasoftware.com/articles/pki-express/python/trusting-in-custom-certificates) e [Ruby](https://docs.lacunasoftware.com/articles/pki-express/ruby/trusting-in-custom-certificates), e [Quais certificados são confiáveis](https://docs.lacunasoftware.com/articles/pki-express/validate-signatures#quais-certificados-são-confiáveis).
## Signer
* Por padrão, uma instância do Signer aceita só certificados ICP-Brasil. Uma instância on-premises só aceita a ICP de testes da Lacuna depois que você adiciona a Lacuna Root Test v3 à seção `TrustArbitrator` dela e, quando os tipos de assinatura estão habilitados, a `SignatureTypes:Simple:TrustArbitrator` e `SignatureTypes:Qualified:TrustArbitrator`. As assinaturas avançadas usam o TrustArbitrator do contexto de segurança delas, que o administrador da instância define na área de administração. Veja [TrustArbitrator](https://docs.lacunasoftware.com/articles/signer/on-premises/trust-arbitrator).
* O cenário de assinatura pública dos [exemplos do Signer](https://github.com/LacunaSoftware/SignerSamples) (.NET e Node.js) assina no sandbox, `https://signer-lac.azurewebsites.net`, com um certificado de teste da Lacuna emitido pela Lacuna CA Test v7 para o CPF 56072386105: o arquivo `nodejs/samples/56072386105.pfx`, senha `1234`. O `identifier` do participante precisa ser igual ao CPF do certificado.
* Para assinar em nuvem pelo widget de assinatura embutida, o exemplo online usa um certificado de teste do BirdID (CPF 026.102.470-10) no ambiente de testes do BirdID, pareado lendo um QR code no aplicativo BirdID. Veja [Assinatura embutida](https://docs.lacunasoftware.com/articles/signer/embedded-signature).
TODO(docs): se o sandbox confia na ICP de testes da Lacuna (os exemplos indicam que sim, mas nenhuma página registra uma execução bem-sucedida) e quais certificados de teste o Dropsigner (`www.dropsigner.com`) aceita.
# [Certificados confiados pela Adobe](https://docs.lacunasoftware.com/articles/pki-guide/adobe-trust)
A partir de dez/2019, a Adobe incluiu a cadeia V5 da ICP-Brasil na sua lista de raízes confiáveis (AATL). Entretanto, a Adobe só considera como
confiáveis certificados A3 e, por isso, implementa uma restrição por política de certificação. Essa restrição possui o efeito colateral de
certicados A3 de determinadas Autoridades Certificadoras, embora perfeitamente válidos segundo a legislação brasileira (assim
como qualquer certificado A1), serem **recusados** na validação de assinaturas de PDFs no Adobe Reader.
Abaixo encontra-se uma lista dos certificados aceitos pela Adobe e, em seguida, uma lista dos rejeitados.
## Certificados aceitos pela Adobe
* A3 da AC PR (2.16.76.1.2.3.1)
* A3 da AC SERPRO (2.16.76.1.2.3.2)
* A3 da AC SERASA CD (2.16.76.1.2.3.3)
* A3 da AC SERPRO RFB (2.16.76.1.2.3.4)
* A3 da AC CERTISIGN MÚLTIPLA (2.16.76.1.2.3.5)
* A3 da AC CERTISIGN RFB (2.16.76.1.2.3.6)
* A3 da AC CAIXA IN (2.16.76.1.2.3.7)
* A3 da AC CAIXA PF (2.16.76.1.2.3.8)
* A3 da AC CAIXA PJ (2.16.76.1.2.3.9)
* A3 da AC SERASA RFB (2.16.76.1.2.3.10)
* A3 da AC IMESP (2.16.76.1.2.3.11)
* A3 da AC PRODEMGE (2.16.76.1.2.3.12)
* A3 da AC SERPRO ACF (2.16.76.1.2.3.13)
* A3 da AC SINCOR (2.16.76.1.2.3.14)
* A3 CORRETOR da AC SINCOR (2.16.76.1.2.3.15)
* A3 da IMESP RFB (2.16.76.1.2.3.16)
* A3 da AC FENACOR (2.16.76.1.2.3.17)
* A3 da AC SERPRO JUS (2.16.76.1.2.3.18)
* A3 da AC CAIXA JUS (2.16.76.1.2.3.19)
* A3 da AC PRODEMGE RFB (2.16.76.1.2.3.20)
* A3 da AC CERTISIGN JUS (2.16.76.1.2.3.21)
* A3 da AC SERASA JUS (2.16.76.1.2.3.22)
* A3 da AC PETROBRAS (2.16.76.1.2.3.23)
* A3 da AC BR RFB (2.16.76.1.2.3.24)
* A3 da AC SINCOR RFB (2.16.76.1.2.3.25)
* A3 da AC FENACON CERTISIGN RFB (2.16.76.1.2.3.26)
* A3 da AC NOTARIAL RFB (2.16.76.1.2.3.27)
* A3 da AC OAB (2.16.76.1.2.3.28)
* A3 da AC PRODERJ (2.16.76.1.2.3.29)
* A3 da AC CMB (2.16.76.1.2.3.30)
* A3 da AC IMPRENSA OFICIAL (2.16.76.1.2.3.31)
* A3 da AC INSTITUTO FENACON (2.16.76.1.2.3.32)
* A3 da AC INSTITUTO FENACON RFB (2.16.76.1.2.3.33)
* A3 da AC PRODEST RFB (2.16.76.1.2.3.34)
* A3 da AC VALID BRASIL (2.16.76.1.2.3.35)
* A3 da AC VALID RFB (2.16.76.1.2.3.36)
* A3 da AC SOLUTI MULTIPLA (2.16.76.1.2.3.37)
* A3 da AC SOLUTI RFB (2.16.76.1.2.3.38)
* A3 da AC DIGITALSIGN (2.16.76.1.2.3.39)
* A3 da AC BOA VISTA CERTIFICADORA (2.16.76.1.2.3.40)
* A3 da AC BOA VISTA RFB (2.16.76.1.2.3.41)
* A3 da AC DIGITALSIGN RFB (2.16.76.1.2.3.42)
* A3 da AC SINCOR RIO (2.16.76.1.2.3.43)
* A3 da AC VALID JUS (2.16.76.1.2.3.44)
* A3 da AC ONLINE BRASIL (2.16.76.1.2.3.45)
* A3 da AC SOLUTI JUS (2.16.76.1.2.3.46)
* A3 da AC VALID PLUS (2.16.76.1.2.3.47)
* A3 da AC SAFEWEB RFB (2.16.76.1.2.3.48)
* A3 da AC CNDL RFB (2.16.76.1.2.3.49)
## Certificados **rejeitados** pela Adobe
* A3 da AC CACB RFB (2.16.76.1.2.3.50)
* A3 da AC DIGITAL (2.16.76.1.2.3.51)
* A3 da AC ONLINE RFB (2.16.76.1.2.3.52)
* A3 da AC EGBA MULTIPLA (2.16.76.1.2.3.53)
* A3 da AC PRONOVA RFB (2.16.76.1.2.3.54)
* A3 da DOCCLOUD RFB (2.16.76.1.2.3.55)
* A3 da AC LINK RFB (2.16.76.1.2.3.56)
* A3 da AC PRONOVA CD (2.16.76.1.2.3.57)
* A3 da AC EGBA RFB (2.16.76.1.2.3.58)
* A3 da AC SIC RFB *em credenciamento (2.16.76.1.2.3.59)
* A3 da AC SIC BRASIL *em credenciamento (2.16.76.1.2.3.60)
* A3 da AC FENACOR RFB (2.16.76.1.2.3.61)
* A3 da AC CONSULTI BRASIL RFB (2.16.76.1.2.3.62)
* A3 da AC A DIGIFORTE RFB *em credenciamento (2.16.76.1.2.3.63)
* A3 da AC SERASA RFB SSL (2.16.76.1.2.3.65)
* A3 da AC SERASA CD SSL (2.16.76.1.2.3.66)
* A3 da AC SAFEWEB *em credenciamento (2.16.76.1.2.3.67)
* A3 da AC CERTISIGN MULTIPLA CODESIGNING (2.16.76.1.2.3.68)
* A3 da AC CERTISIGN RFB CODESIGNING (2.16.76.1.2.3.69)
* A3 da AC CERTISIGN JUS CODESIGNING (2.16.76.1.2.3.70)
* A3 da AC CERTISIGN JUS SSL (2.16.76.1.2.3.71)
* A3 da AC CERTISIGN MULTIPLA SSL (2.16.76.1.2.3.72)
* A3 da AC CERTISIGN RFB SSL (2.16.76.1.2.3.73)
* A3 da AC DIGITALSIGN SSL (2.16.76.1.2.3.74)
* A3 da AC SOLUTI MULTIPLA SSL (2.16.76.1.2.3.76)
* A3 da AC SOLUTI MULTIPLA CODESIGNING (2.16.76.1.2.3.77)
* A3 da AC SOLUTI JUS SSL (2.16.76.1.2.3.82)
* A3 da AC SOLUTI JUS CODESIGNING (2.16.76.1.2.3.83)
* A3 da AC SERPRO ACF CODESIGNING (2.16.76.1.2.3.84)
* A3 da AC VALID BRASIL SSL (2.16.76.1.2.3.201)
* A3 da AC VALID BRASIL CODESIGNING (2.16.76.1.2.3.202)
* A3 da AC VALID PLUS SSL (2.16.76.1.2.3.203)
* A3 da AC VALID PLUS CODESIGNING (2.16.76.1.2.3.204)
* A3 da AC VALID RFB SSL (2.16.76.1.2.3.205)
* A3 da AC VALID RFB CODESIGNING (2.16.76.1.2.3.206)
* A3 da AC VALID JUS SSL (2.16.76.1.2.3.207)
* A3 da AC VALID JUS CODESIGNING (2.16.76.1.2.3.208)
* A3 da AC IMPRENSA OFICIAL SSL (2.16.76.1.2.3.209)
* A3 da AC IMPRENSA OFICIAL SP RFB SSL (2.16.76.1.2.3.210)
# [Lacuna Bulk Signer](https://docs.lacunasoftware.com/articles/bulk-signer)
O Lacuna Bulk Signer é um **serviço *on-premises* de assinatura digital em lote** para cenários
compatíveis com a ICP-Brasil. Ele recebe arquivos de origens automatizadas (pastas monitoradas ou
upload via REST) e de operadores (upload pelo dashboard), processa-os em um pipeline de assinatura
controlado — com uma etapa opcional de aprovação humana, na qual os aprovadores podem coassinar com os
próprios certificados — e gera saídas assinadas e verificadas, com histórico operacional e log de
eventos completos, um dashboard para o operador e recuperação automática após uma reinicialização.
O Bulk Signer foi projetado para rodar na sua própria infraestrutura: um único serviço que monitora
pastas (ou aceita uploads), assina, verifica e move os resultados para uma pasta de saída. Não há
atualização automática, e uma instalação padrão não abre nenhuma conexão de saída — assinatura remota,
Azure Key Vault, certificados em nuvem para aprovadores e telemetria são todos opcionais e precisam ser
ativados.
> **Dica: O que há de novo desde a 2.0**
>
> Esta documentação descreve a versão **2.18.0**. As principais novidades desde a 2.0:
>
> - **Os perfis de assinatura ficam no banco de dados operacional** e são criados, editados,
> recertificados e aposentados pelo dashboard; cada perfil escolhe a pasta monitorada da qual recebe
> arquivos (2.1–2.2).
> - **Os aprovadores podem assinar**: uma regra de aprovação pode exigir os certificados dos próprios
> aprovadores, no navegador ou — pelo Lacuna CloudHub — em um provedor em nuvem, um arquivo por vez ou
> uma seleção inteira de uma vez; um perfil pode até não ter chave e ser assinado apenas pelos
> aprovadores (2.1, 2.7, 2.14).
> - **Uploads pelo dashboard**, que podem ser desligados em um host (2.4, 2.10); um arquivo com um nome
> que já foi assinado é recusado, em vez de ser assinado duas vezes (2.13).
> - **O log de eventos operacionais pode ser consultado** no dashboard e pela API REST, e um job pode ser
> excluído individualmente pela página Jobs (2.13); a página Jobs também exporta para Excel e baixa
> vários arquivos assinados em um único ZIP (2.7, 2.11).
> - **O Limpar Jobs esvazia o sistema**: todos os jobs, os arquivos deles e os eventos operacionais
> (2.9–2.10).
> - **Reimplantações em cluster no App Service** deslocam o container antigo, em vez de falhar ao iniciar
> (2.5).
> - A verificação da data de pagamento do CNAB240 pode ser desligada por perfil (2.15).
> - **Assinatura em uma pasta do Lacuna Signer**: um perfil vinculado a uma pasta do Signer assina, com o
> próprio certificado, os documentos que as pessoas criam ali para o titular do certificado — encontrados
> por uma varredura e, opcionalmente, em segundos, por um webhook do Signer (2.16).
> - **Um P7S simples em vez da ADR-Básica**: um perfil CAdES pode assinar um CMS comum com SHA-1, sem
> política de assinatura, para um destinatário cujo sistema não lê nada mais novo — inclusive as
> assinaturas dos aprovadores, onde eles assinam (2.17–2.18).
## Funcionalidades
- **Formatos de assinatura.** CAdES (`.p7m`), PAdES (PDF) e XAdES (XML) — todos sob a política
**ADR-Básica** da ICP-Brasil por padrão; um perfil CAdES pode assinar, em vez dela, um **P7S simples**
(SHA-1, sem política de assinatura), para um destinatário que não lê nada mais novo. A nomenclatura da
saída, definida por perfil, preserva a extensão original (`remessa.signed.rem`) ou grava o CAdES em
formato PEM quando o sistema de destino exigir.
- **Origens de certificado.** Arquivos PKCS#12 (`.pfx` / `.p12`), HSMs e smart cards PKCS#11, o
repositório de certificados do Windows e o **Azure Key Vault** (a chave permanece no cofre, e a
assinatura é feita remotamente). O `.pfx` ou o `.cer` pode ser lido do **Azure Blob Storage** em vez
do disco local, ou um PKCS#12 pode ser **enviado pelo dashboard**, que o mantém criptografado no banco
operacional.
- **Perfis de assinatura, gerenciados no dashboard.** Cada perfil, identificado por um nome, reúne
formato, origem do certificado, verificação, criptografia, nomenclatura da saída, validação CNAB240 e
regra de aprovação. Os perfis são linhas no banco operacional: crie, edite, recertifique e aposente
perfis pelo dashboard — uma mudança de comportamento vale para o próximo job, sem reinicialização — e
cada perfil escolhe a única pasta monitorada da qual recebe arquivos. O `Signing:Profiles[]` na
configuração é um seed (carga inicial) usado uma única vez, no primeiro boot.
- **Três caminhos de entrada.** Pastas de entrada monitoradas (com um detector de estabilidade, para que
arquivos gravados pela metade não sejam capturados cedo demais), um endpoint `POST /api/files` para
clientes programáticos e um botão **Enviar arquivos** na página Jobs do dashboard. `Upload:Enabled =
false` desliga os dois caminhos de upload. Um arquivo que chega com um nome que um job concluído ou
ativo já possui é recusado, em vez de ser assinado duas vezes.
- **Arquivos de pagamento CNAB240.** Opcional por perfil: interpreta uma remessa do Banco do Brasil,
recusa-se a assinar uma remessa que não esteja em conformidade ou cujas datas de pagamento já tenham
passado — a menos que o perfil desligue essa verificação de datas, para um banco que processa
pagamentos com data passada — e mostra ao operador o total, o pagador e cada pagamento individual.
- **Etapa de aprovação.** Retém um arquivo de pagamento até a decisão de um quórum de aprovadores
designados, antes que exista qualquer assinatura. Uma única rejeição é um veto, e o arquivo rejeitado é
devolvido à pasta de saída marcado como `.reject`; as aprovações são vinculadas aos bytes do arquivo, e
a regra é congelada no job, de modo que editar um perfil nunca libera um arquivo retido. Os aprovadores
têm uma fila própria, com aprovação em lote e exportação para Excel — e um **segundo fator TOTP**
opcional, que pede ao aprovador que comprove a própria presença antes de decidir.
- **Aprovadores que assinam.** Uma regra de aprovação pode exigir que os aprovadores aprovem
**coassinando o arquivo com o próprio certificado ICP-Brasil** — junto com a chave do perfil ou no
lugar dela, em um perfil sem chave, assinado apenas pelos aprovadores. O certificado pode estar no
navegador do aprovador (Lacuna Web PKI) ou ser mantido por um provedor em nuvem, pelo **Lacuna
CloudHub**, e uma seleção inteira pode ser aprovada e assinada de uma só vez.
- **Pipeline recuperável.** Os jobs passam por uma fila durável, com pausa/retomada, que sobrevive a
reinicializações. Se o serviço for interrompido no meio do processamento, uma varredura de recuperação
na inicialização separa qualquer job interrompido, de modo que nada se perca silenciosamente.
- **Criptografia pós-assinatura opcional (BSENC v1).** Quando habilitada, criptografa em repouso os
artefatos assinados, com AES-256-GCM. Inclui scripts de referência de descriptografia em Python e
PowerShell.
- **Integração com o Lacuna Signer (por perfil).** Encaminha uma pasta para o
[Lacuna Signer](https://www.lacunasoftware.com/) para assinatura humana, em vez de assinar com um
certificado mantido no host — ou, na direção oposta, vincula um perfil a uma pasta do Lacuna Signer
e assina, com o próprio certificado, os documentos que as pessoas criam ali para o titular do
certificado.
- **Autenticação, de duas formas.** Uma única chave de API atende tanto ao dashboard do operador (por
meio de um cookie de sessão) quanto aos clientes programáticos (pelo header `X-API-Key`) — ou ative o
login opcional pelo **Microsoft Entra ID**, com os app roles `Administrator` e `Approver`, sem alterar
a chave da API REST.
- **Dashboard do operador, em inglês ou português do Brasil.** Um console web com status ao vivo,
histórico de jobs, ações de nova tentativa, cancelamento, nova varredura e exclusão de jobs
individuais, uploads, exportação da lista de jobs para Excel, download de vários arquivos assinados em
um ZIP, as páginas de perfis de assinatura, um visualizador de exceções recentes e o **log de eventos
operacionais** (quem pausou o pipeline, alterou um perfil, decidiu uma aprovação, limpou os jobs). As
páginas de login e do aprovador podem exibir o logotipo do cliente. O idioma é escolhido pelo leitor,
em cada navegador, e não configurado no servidor.
- **Armazenamento e banco de dados, locais ou no Azure.** A árvore de trabalho pode ficar em disco local
ou em um compartilhamento do **Azure Files**; o banco operacional pode continuar em SQLite ou migrar
para **SQL Server / Azure SQL**, sob o seu próprio regime de backup e DR. As duas escolhas são
independentes.
- **Escala horizontal no Azure App Service (opcional).** `Cluster:Enabled` executa mais de uma
instância ativa sobre um único banco operacional e um único compartilhamento de trabalho: um job
nunca é processado duas vezes, o trabalho de uma instância que cai é assumido por outra em vez de
ficar órfão, e o pipeline continua assinando enquanto um host está fora do ar. Vem desabilitado por
padrão e, desabilitado, é byte a byte o produto de instância única. Veja
**[Azure App Service](https://docs.lacunasoftware.com/articles/bulk-signer/azure)** e, antes disso, **[os limites desse modo](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability)**.
- **Backup do banco de dados (implantações com SQLite).** Backups agendados ou sob demanda do banco
operacional para um caminho local, um bucket compatível com S3 ou um container do Azure Blob, com
retenção por quantidade de cópias.
- **Visibilidade de desempenho.** Um painel de tempos por etapa (espera na fila, assinatura,
verificação, saída) com vazão e uma divisão entre Local e Remoto — mantido no banco operacional, de
modo que sobrevive a reinicializações e descreve um cluster inteiro —, além da exportação opcional
para o Azure Application Insights.
- **Observabilidade.** Logs estruturados com mascaramento automático de segredos e um destino opcional
em **Azure Table** para hosts cujo disco não sobrevive a uma reinicialização, um endpoint de métricas
Prometheus, um probe de prontidão cuja resposta anônima é apenas o veredito (o detalhe exige a chave
de API) e um envelope de erro `ProblemDetails` (RFC 9457) com códigos estáveis, legíveis por máquina.
- **Limite de requisições (rate limiting) por IP.** Limites configuráveis de janela fixa nos endpoints
de upload, ações, aprovação e exportação, com suporte opcional a headers encaminhados, para que o
cliente real seja identificado mesmo por trás de um proxy ou balanceador de carga.
- **Implantação em múltiplos alvos.** O mesmo serviço roda como unidade systemd no Linux, Serviço do
Windows, container Docker, Azure Web App ou processo de console em primeiro plano.
## Como funciona
[](https://docs.lacunasoftware.com/images/bulk-signer/how-it-works-pt.png)
*Clique no diagrama para abri-lo em tamanho real.*
O mesmo pipeline, com as etapas pelos nomes que o serviço usa:
```text
pasta input/ ──────┐
POST /api/files ───┼──▶ Fila ──▶ Claim ──▶ [gates] ──▶ Assina ──▶ Verifica ──┬──▶ output/
upload no dashboard┘ │ (output/*.enc
em caso de │ quando a cripto-
falha └──▶ error/ grafia
está ativa)
[gates], ambos opcionais por perfil de assinatura e totalmente ignorados quando não configurados:
Parse CNAB240 — recusa uma remessa não conforme, ou cujas datas de pagamento já passaram
Etapa de aprovação — retém em AwaitingApproval até que um quórum de pessoas nomeadas aprove
(e, quando a regra assim diz, coassine com os próprios certificados)
```
Cada etapa é registrada no banco operacional (histórico de jobs + eventos operacionais) e no arquivo de
log estruturado; os eventos podem ser consultados na página Eventos do dashboard. O dashboard e a API
REST leem os mesmos dados e disparam as mesmas ações.
## Início rápido — Docker
Com o pacote de implantação fornecido pela Lacuna Software e a imagem do repositório privado de imagens
Docker da Lacuna — veja [Obtendo o produto](https://docs.lacunasoftware.com/articles/bulk-signer/installation#obtendo-o-produto):
```bash
cd deploy/docker
docker login --username # o compose nomeia o repositório
cp .env.sample .env
mkdir -p data logs config
cp ../appsettings.Production.json.sample config/appsettings.Production.json
# Edite config/appsettings.Production.json e .env — no mínimo:
# - Signing__PkiSdkLicense (string de licença em base64 fornecida pela Lacuna Software)
# - Auth__ApiKey (>= 16 caracteres; use um valor aleatório)
# - Signing:Certificate:Pfx:Path (e um arquivo .pfx irmão em config/) — ou escolha outra origem
sudo chown -R 1654:1654 data logs # o container roda como UID 1654 em hosts Linux
docker compose up -d
curl http://localhost:8080/api/health
```
Entre no dashboard em `http://localhost:8080/` com a `Auth:ApiKey` configurada.
Para instalações como unidade systemd no Linux, como serviço do Windows ou em primeiro plano, veja
**[Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation)**.
## Documentação
| Assunto | Página |
|---------|--------|
| Instalar o serviço em qualquer alvo suportado | [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation) |
| Escalar horizontalmente no Azure App Service, passo a passo | [Azure App Service (modo cluster)](https://docs.lacunasoftware.com/articles/bulk-signer/azure) |
| O que rodar mais de uma instância *não* lhe dá | [Alta disponibilidade e seus limites](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability) |
| Cada chave do `appsettings.json` (tipo, padrão, override por ambiente) | [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration) |
| Escolher e configurar uma origem de certificado (PFX / PKCS#11 / repositório do Windows / Azure Key Vault) | [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates) |
| Tratamento de segredos, rotação da chave de API, ACLs de arquivos, mascaramento de logs | [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) |
| Operação do dia a dia e o ciclo de vida do job | [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations) |
| O console Blazor do operador | [Dashboard](https://docs.lacunasoftware.com/articles/bulk-signer/dashboard) |
| Interpretar o painel de tempos por etapa | [Estatísticas de jobs](https://docs.lacunasoftware.com/articles/bulk-signer/statistics) |
| Exportação opcional para o Azure Application Insights | [Telemetria](https://docs.lacunasoftware.com/articles/bulk-signer/telemetry) |
| Os endpoints REST e o envelope de erro identificado por `code` | [API REST](https://docs.lacunasoftware.com/articles/bulk-signer/rest-api) |
| Criptografia pós-assinatura (BSENC v1) | [Criptografia](https://docs.lacunasoftware.com/articles/bulk-signer/encryption) |
| Encaminhar uma pasta pelo Lacuna Signer para assinatura humana | [Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer) |
| Interpretar e validar arquivos de pagamento do Banco do Brasil | [Arquivos de pagamento CNAB240](https://docs.lacunasoftware.com/articles/bulk-signer/cnab240) |
| Reter um arquivo de pagamento até a decisão de um quórum de aprovadores | [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals) |
| Padrões de retenção e o que é (e o que não é) removido automaticamente hoje | [Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention) |
| Modos de falha e diagnóstico | [Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting) |
| Scripts de referência — descriptografia, provisionamento do Key Vault, registro de aplicativo no Entra | [Exemplos](https://docs.lacunasoftware.com/articles/bulk-signer/samples) |
Com o serviço em execução, uma referência OpenAPI ao vivo é servida em `/scalar/v1`.
## Ordem de leitura
| Se você está… | Comece em |
|---------------|-----------|
| Instalando o serviço pela primeira vez | [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation) → [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration) → [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates) |
| Conectando um sistema automatizado à API REST | [API REST](https://docs.lacunasoftware.com/articles/bulk-signer/rest-api) → [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) → [Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting) |
| Operando uma instalação existente | [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations) → [Dashboard](https://docs.lacunasoftware.com/articles/bulk-signer/dashboard) → [Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting) |
| Roteando uma pasta monitorada para um perfil de assinatura, ou descobrindo por que os arquivos de uma pasta não estão sendo processados | [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#roteando-uma-pasta-monitorada-para-um-perfil-de-assinatura) → [Dashboard](https://docs.lacunasoftware.com/articles/bulk-signer/dashboard) → [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#storageinputsprofile--roteamento-por-pasta) |
| Descobrindo quem pausou o pipeline, alterou um perfil, decidiu uma aprovação ou limpou os jobs | [Dashboard](https://docs.lacunasoftware.com/articles/bulk-signer/dashboard) → [API REST](https://docs.lacunasoftware.com/articles/bulk-signer/rest-api) → [Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention) |
| Limpando os jobs, ou excluindo um | [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#limpar-jobs) → [Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention) |
| Diagnosticando vazão baixa | [Estatísticas de jobs](https://docs.lacunasoftware.com/articles/bulk-signer/statistics) → [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates) → [Telemetria](https://docs.lacunasoftware.com/articles/bulk-signer/telemetry) |
| Mantendo a chave de assinatura fora do host | [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#origem--azurekeyvault) → [Exemplos](https://docs.lacunasoftware.com/articles/bulk-signer/samples) → [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) |
| Habilitando a criptografia | [Criptografia](https://docs.lacunasoftware.com/articles/bulk-signer/encryption) → [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) → [Exemplos](https://docs.lacunasoftware.com/articles/bulk-signer/samples) |
| Encaminhando uma pasta pelo Lacuna Signer (assinatura humana) | [Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer) → [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration) → [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations) |
| Assinando arquivos de pagamento bancário | [Arquivos de pagamento CNAB240](https://docs.lacunasoftware.com/articles/bulk-signer/cnab240) → [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals) → [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) |
| Colocando uma etapa de aprovação antes do assinador | [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals) → [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofilesapproval--a-etapa-de-aprovação) → [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) |
| Autenticando com contas organizacionais | [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation#login-pelo-microsoft-entra-id-opcional) → [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#authentraid--login-opcional-pelo-microsoft-entra-id) → [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) |
| Rodando sem disco local durável | [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#storageprovider--storageazurefiles--o-compartilhamento-de-trabalho) → [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation#escolhendo-onde-fica-o-banco-operacional) → [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#lendo-o-arquivo-de-um-blob) |
| Rodando mais de uma instância | [Alta disponibilidade e seus limites](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability) → [Azure App Service](https://docs.lacunasoftware.com/articles/bulk-signer/azure) → [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#cluster--implantação-com-múltiplas-instâncias) |
| Preservando o fluxo de logs quando o disco do host não sobrevive | [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#loggingazuretable--um-segundo-destino-de-log) → [Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention#logs-em-uma-tabela--nada-os-poda) |
| Pedindo um segundo fator aos aprovadores | [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals#provando-que-é-você) → [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#approversecondfactor) → [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) |
| Fazendo backup do banco operacional | [Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention#disciplina-de-backup) → [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#backup) |
# [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation)
O Lacuna Bulk Signer é um único serviço que pode rodar em quatro alvos suportados:
| Alvo | Modelo de processo | Ciclo de vida gerenciado por |
|------|--------------------|------------------------------|
| systemd no Linux | Serviço em segundo plano | `systemctl` |
| Serviço do Windows | Serviço em segundo plano | `services.msc` / `sc.exe` |
| Docker / Compose | Container | `docker compose` |
| Console (pontual / teste) | Primeiro plano | Operador (`Ctrl+C`) |
O mesmo binário suporta os quatro. O banner de inicialização imprime uma linha `host mode = …` que
informa qual ciclo de vida está de fato ativo.
Você baixa a aplicação da Lacuna: a **imagem de container**, do repositório privado de imagens Docker
da Lacuna, ou os **binários publicados**, de uma URL de download específica do sistema operacional que
contém o identificador único da sua organização. Os scripts de instalação por alvo e o arquivo de
configuração de exemplo comentado chegam separadamente, no **pacote de implantação**. A seção
[Obtendo o produto](https://docs.lacunasoftware.com/articles/bulk-signer/installation#obtendo-o-produto) trata dos dois e das credenciais que cada um exige.
## Escolha seu alvo
| Onde o serviço vai rodar? | Use |
|---------------------------|-----|
| Servidor Linux | systemd — `deploy/linux/install.sh` |
| Servidor Windows | Serviço do Windows — `deploy/windows/Install-Service.ps1` |
| Qualquer host com Docker | Container — `deploy/docker/docker-compose.yml` |
| Azure, em mais de uma instância | **[Azure App Service (modo cluster)](https://docs.lacunasoftware.com/articles/bulk-signer/azure)** — página própria |
| Apenas testando localmente | Console — execute o executável publicado em primeiro plano |
Todos os alvos desta página são de **instância única**, que é o que este produto é, a menos que você
ative deliberadamente o modo cluster. Rodar duas instâncias dessas sobre um mesmo compartilhamento de
trabalho é um risco documentado, e não uma implantação — veja
[Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quando-outra-instância-parece-ser-dona-do-compartilhamento-de-trabalho). A
única topologia com múltiplas instâncias suportada é um Azure Web App com escala horizontal em
um único App Service Plan, e ela tem seu próprio passo a passo
([Azure App Service](https://docs.lacunasoftware.com/articles/bulk-signer/azure)) e sua própria lista de limites
([Alta disponibilidade](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability)).
## Obtendo o produto
Não há download público, e nada aqui é compilado a partir do código-fonte. A Lacuna Software distribui
a aplicação de duas formas, e o alvo que você acabou de escolher define qual delas você usa:
| O que você baixa | De onde | Para quais alvos |
|------------------|---------|------------------|
| A **imagem de container**, já pronta | Do repositório privado de imagens Docker da Lacuna, como `/bulksigner:` | Docker / Compose, [Azure App Service](https://docs.lacunasoftware.com/articles/bulk-signer/azure) |
| Os **binários publicados**, um arquivo por sistema operacional | De uma URL de download que contém o identificador único da sua organização | systemd no Linux, Windows Service, console |
Nenhum dos dois artefatos traz os scripts de instalação. Eles vêm no **pacote de implantação** — um
único arquivo, o mesmo para todos os sistemas operacionais, com a árvore `deploy/`, o
`appsettings.Production.json.sample` comentado e os scripts auxiliares em PowerShell documentados em
[Exemplos](https://docs.lacunasoftware.com/articles/bulk-signer/samples). Descompacte-o na máquina de onde você vai instalar: todos os caminhos `deploy/…`
desta página são relativos à raiz dele.
A Lacuna fornece a você três itens, e eles não são intercambiáveis:
| O que você recebe | O que destrava | Se vazar ou expirar |
|-------------------|----------------|---------------------|
| **Credenciais de registry** — um usuário e um token de acesso | O pull da imagem do repositório privado | Peça a reemissão à Lacuna. O token é restrito à sua organização e pode ser revogado individualmente. |
| **Um identificador único** | As URLs de download dos binários | Peça a reemissão à Lacuna. Ele identifica a sua organização, e não uma versão — um mesmo identificador serve a todos os sistemas operacionais. |
| **A string de licença do PKI SDK** | O serviço **em execução**, em todos os alvos — não o download | Não é uma credencial de distribuição; veja [Obtendo a licença do PKI SDK](https://docs.lacunasoftware.com/articles/bulk-signer/installation#obtendo-a-licença-do-pki-sdk). |
> **Aviso: O identificador em uma URL de download é uma credencial**
>
> Ele é a única proteção entre essa URL e qualquer pessoa que a tenha, então uma URL que o contém não deve
> aparecer em um ticket público, em um log de CI compartilhado, em uma página de wiki ou em um script
> versionado. Guarde-o onde você guarda o token do registry — e, se ele vazar, peça a reemissão à Lacuna
> em vez de contar com que o link continue desconhecido.
### A imagem de container
```bash
docker login --username # pede o token de acesso
docker pull /bulksigner:
```
A Lacuna fornece juntos o host do registry, o caminho do repositório e as credenciais. O repositório
é privado, então um pull sem autenticação responde `not found` em vez de `unauthorized` — essa é a
resposta habitual do Docker para um repositório que suas credenciais não conseguem ver, e não um sinal de
que você digitou o nome errado.
**Fixe a ``.** Uma tag `latest` muda com o tempo, e em um host de containers isso significa que
um restart pode subir uma versão que você não escolheu instalar.
Nenhum build é feito localmente: a imagem que a Lacuna publica é a que roda, baseada no Ubuntu
24.04 LTS pelo motivo explicado em [Docker / Compose](https://docs.lacunasoftware.com/articles/bulk-signer/installation#docker--compose), abaixo.
### Os binários publicados
Baixe o arquivo correspondente ao sistema operacional do host e extraia-o em um local que o script de
instalação consiga ler:
```bash
# Linux
curl -fL -o bulksigner-linux-x64.tar.gz \
"https://cdn.lacunasoftware.com/bulk-signer//linux-x64.tar.gz"
mkdir -p publish && tar -xzf bulksigner-linux-x64.tar.gz -C publish
```
```powershell
# Windows — um prompt comum basta aqui; só a instalação em si precisa de elevação
Invoke-WebRequest -Uri "https://cdn.lacunasoftware.com/bulk-signer//win-x64.zip" -OutFile bulksigner-win-x64.zip
Expand-Archive -Path bulksigner-win-x64.zip -DestinationPath publish
```
`publish` é o nome que o resto desta página usa, porque é o que os scripts de instalação recebem em
`--from publish` / `-From publish` — o nome é indiferente para o arquivo compactado, e a flag aceita
qualquer caminho. Fale com a Lacuna se você precisar de um sistema operacional ou de uma arquitetura que
essas duas URLs não cobrem.
### Um host sem rota para a internet
Nenhum dos dois artefatos precisa de acesso à internet no momento da instalação: ambos são
autossuficientes, sem feed de pacotes, sem etapa de restore e sem nenhuma conexão posterior com a origem —
então baixar em uma estação conectada e copiar para o host resolve por completo. Leve os binários no
arquivo compactado; leve a imagem com `docker save` / `docker load`, ou publique-a em um registry que o
host alcance, que é o que o
[passo 1](https://docs.lacunasoftware.com/articles/bulk-signer/azure#1-importe-a-imagem) do passo a passo do Azure faz, por outro motivo. A licença do PKI
SDK é uma string, e não um download, então uma instalação isolada da rede continua isolada.
### Depois, confira o que você obteve
A versão realmente em execução aparece na barra superior do dashboard, em todas as páginas, é impressa
por completo abaixo do banner com a marca exibido no console a cada início, e também está na página
Sistema. Depois de concluir a instalação abaixo, compare-a com a versão que você deveria instalar — é a
única confirmação de que a URL, ou a tag, entregou o que você esperava.
## Pré-requisitos — comuns a todos os alvos
1. **String de licença do Lacuna PKI SDK** (base64), fornecida pela Lacuna Software. Obrigatória na
inicialização; sem ela, o serviço se recusa a subir. Veja
[Obtendo a licença do PKI SDK](https://docs.lacunasoftware.com/articles/bulk-signer/installation#obtendo-a-licença-do-pki-sdk).
2. **Uma origem de certificado de assinatura.** Escolha uma:
- **PFX** — um arquivo `.pfx` / `.p12` mais a senha que o destrava.
- **PKCS#11** — um driver do fabricante (`.so` no Linux, `.dll` no Windows), mais o thumbprint
SHA-1 do certificado de assinatura no token, mais o PIN fornecido por variável de ambiente.
- **Repositório de certificados do Windows** — apenas em alvos Windows, mais o thumbprint SHA-1.
Veja os detalhes em [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates). O certificado que você configura é o seed (carga
inicial) do primeiro **perfil de assinatura** no primeiro boot; daí em diante, os perfis — incluindo o
certificado — ficam no banco de dados operacional e são gerenciados pelo dashboard (veja
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofiles--perfis-de-assinatura-por-pasta)). Se o certificado
de um perfil tem um segredo — uma senha de PFX, um segredo de aplicativo do Azure Key Vault, uma
credencial de blob —, defina também o `Signing:ProfileSecretsKey` **antes** do primeiro boot e faça
backup dele: a importação é recusada sem ele, e perdê-lo depois significa digitar esses segredos de
novo.
3. **Decisão sobre criptografia.** Deixe desabilitada (padrão) ou habilite o BSENC v1. Se habilitar a
criptografia, decida onde a senha e o salt vão ficar antes do primeiro boot. Veja
[Criptografia](https://docs.lacunasoftware.com/articles/bulk-signer/encryption).
4. **Terminação TLS.** O serviço escuta em HTTP puro por padrão. A implantação recomendada faz a
terminação TLS em um proxy reverso (nginx, IIS, Traefik). A flag `Hosting:RequireHttps` (padrão
`false`) controla o redirecionamento HTTPS no próprio processo — defina-a como `true` apenas se você tiver
configurado um certificado no Kestrel.
5. **Pastas de entrada monitoradas.** Decida se você precisa de uma pasta de entrada (padrão) ou de
várias. Com uma única pasta, omita `Storage:Inputs[]` por completo — o serviço cria uma chamada
`default` em `{Root}/input`. Para múltiplas pastas, preencha `Storage:Inputs[]` com uma entrada
por pasta; veja [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#storage). O perfil de assinatura usado para assinar
os arquivos de cada pasta é **escolhido na página do perfil depois do primeiro boot**, uma pasta por
perfil; a chave `Profile` de uma pasta só serve de seed para essa escolha no primeiro boot (veja
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#storageinputsprofile--roteamento-por-pasta)).
Toda instalação cria uma configuração de produção editável a partir do
`appsettings.Production.json.sample` fornecido. O exemplo vem comentado com marcadores `REQUIRED` e
`SECRET`; revise-o antes do primeiro start.
## Obtendo a licença do PKI SDK
A licença é uma string base64 fornecida pela Lacuna Software. Há duas formas de carregá-la:
| Onde | Como |
|------|------|
| Variável de ambiente (preferencial) | Defina `Signing__PkiSdkLicense=` |
| Arquivo de configuração | Defina `Signing:PkiSdkLicense` em `appsettings.Production.json` |
A variável de ambiente tem precedência no boot. Os scripts de instalação leem a variável de ambiente
do arquivo específico de cada alvo (`/etc/bulksigner/bulksigner.env` no Linux, variáveis de ambiente
de escopo de máquina no Windows, `.env` no Docker), de modo que a licença nunca vai parar em um arquivo
versionado. Veja em [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) o tratamento completo de segredos.
> **Aviso: Atualizando a partir da 1.0.x**
>
> Esta chave se chamava `Signing:License` (`Signing__License`) na 1.0.x e foi renomeada na **1.1.0**. O
> nome antigo não é mais lido, então uma instalação atualizada que ainda o define falha na inicialização
> com `Signing:PkiSdkLicense is required`. Renomeie a chave no seu arquivo de configuração ou de
> ambiente como parte da atualização.
## Linux — systemd
```bash
# 1. Com os binários extraídos em publish/ e o pacote de implantação descompactado na
# máquina de destino (veja Obtendo o produto acima):
sudo bash deploy/linux/install.sh --from publish
# 2. Edite a configuração de produção e o arquivo de ambiente com os segredos.
sudo nano /etc/bulksigner/appsettings.Production.json
sudo nano /etc/bulksigner/bulksigner.env
# 3. Reinicie para que as mudanças de configuração tenham efeito.
sudo systemctl restart bulksigner
# 4. Verifique se o serviço está no ar.
curl http://localhost:8080/api/health
curl http://localhost:8080/api/ready
systemctl --no-pager status bulksigner
journalctl -u bulksigner -f
```
Caminhos de instalação (convenções FHS):
| Caminho | Finalidade | Modo | Dono |
|---------|------------|------|------|
| `/opt/bulksigner` | Binário (somente leitura após a instalação) | `0755` | `root:root` |
| `/var/lib/bulksigner` | Dados: `input` / `processing` / `output` / `db` | `0750` | `bulksigner:bulksigner` |
| `/var/log/bulksigner` | Arquivos de log duráveis | `0750` | `bulksigner:bulksigner` |
| `/etc/bulksigner` | `appsettings.Production.json` + `bulksigner.env` | `0750` | `bulksigner:bulksigner` |
A unit do systemd usa `Type=notify`, de modo que `systemctl status` só informa `active (running)` depois
que todo o bootstrap (carregamento da licença + migração do banco + recuperação do pipeline) for concluído
com sucesso. As flags de hardening (`NoNewPrivileges`, `ProtectSystem=strict`, `PrivateTmp`) vêm
ativadas por padrão.
**Desinstalação:**
```bash
sudo bash deploy/linux/uninstall.sh # para + remove a unit, preserva os dados
sudo bash deploy/linux/uninstall.sh --purge # também apaga dados, logs, configuração e o usuário de sistema
```
## Windows — serviço do Windows
```powershell
# 1. Com os binários extraídos em publish\ e o pacote de implantação descompactado na máquina de
# destino (veja Obtendo o produto acima), em um prompt do PowerShell COM PRIVILÉGIOS ELEVADOS:
.\deploy\windows\Install-Service.ps1 -From publish
# 2. Edite a configuração de produção:
notepad C:\ProgramData\Lacuna\BulkSigner\config\appsettings.Production.json
# 3. Defina os segredos como variáveis de ambiente de escopo de máquina:
[Environment]::SetEnvironmentVariable("Signing__PkiSdkLicense", "", "Machine")
[Environment]::SetEnvironmentVariable("Auth__ApiKey", "", "Machine")
[Environment]::SetEnvironmentVariable("BULK_SIGNER_PKCS11_PIN", "", "Machine")
[Environment]::SetEnvironmentVariable("BULK_SIGNER_ENCRYPTION_PASSWORD", "", "Machine")
Restart-Service LacunaBulkSigner
# 4. Verifique.
Invoke-WebRequest http://localhost:8080/api/health
Invoke-WebRequest http://localhost:8080/api/ready
Get-Service LacunaBulkSigner
Get-Content C:\ProgramData\Lacuna\BulkSigner\logs\bulksigner-*.log -Tail 50 -Wait
```
Caminhos de instalação (convenções do Windows):
| Caminho | Finalidade |
|---------|------------|
| `C:\Program Files\Lacuna\BulkSigner` | Binário (somente leitura após a instalação) |
| `C:\ProgramData\Lacuna\BulkSigner\config` | `appsettings.Production.json` |
| `C:\ProgramData\Lacuna\BulkSigner\data` | Dados operacionais (`input` / `processing` / `output` / `db`) |
| `C:\ProgramData\Lacuna\BulkSigner\logs` | Arquivos de log |
O serviço roda com uma **conta virtual** (`NT SERVICE\LacunaBulkSigner`) — sem senha de operador para
gerenciar nem conta de domínio para receber permissões. O script de instalação concede a essa conta acesso
à árvore em `ProgramData` e configura a recuperação em caso de falha (reiniciar após 5 s na primeira e
na segunda falha, 30 s na terceira).
> **Observação**
>
> Os logs da aplicação vão apenas para o destino de arquivo. O log de eventos Aplicativo do Windows
> registra as entradas de ciclo de vida do serviço (start / stop / falha) — e não as linhas de log de cada
> job. Procure essas linhas no arquivo de log.
**Desinstalação:**
```powershell
.\deploy\windows\Uninstall-Service.ps1 # para + remove o serviço, preserva os dados
.\deploy\windows\Uninstall-Service.ps1 -Purge # também apaga o ProgramData e as variáveis de ambiente de máquina
```
## Docker / Compose
```bash
cd deploy/docker
# 1. Autentique-se no registry privado da Lacuna — a linha image: do compose o nomeia.
docker login --username
# 2. Prepare os diretórios de trabalho no host.
cp .env.sample .env
mkdir -p data logs config
cp ../appsettings.Production.json.sample config/appsettings.Production.json
# 3. Edite a configuração e o arquivo de ambiente.
nano config/appsettings.Production.json
nano .env
# 4. O container roda como UID 1654. Em hosts Linux:
sudo chown -R 1654:1654 data logs
# 5. Suba — o `up` faz o pull da imagem na primeira execução.
docker compose up -d
# 6. Verifique.
curl http://localhost:8080/api/health
docker compose ps # deve mostrar "healthy" após ~30 s
docker compose logs -f bulksigner
```
É na linha `image:` do arquivo do compose que ficam o repositório privado e a tag fixada, então é essa a
linha que você edita na atualização. Um pull que falha aparece como um container que nunca inicia e um
log de aplicação vazio — porque ainda não há aplicação —, então confira o `docker login` antes de tirar
conclusões do silêncio.
A Lacuna gera a imagem sobre o Ubuntu 24.04 LTS — **não** sobre o Alpine. As bibliotecas `.so` de HSM
geralmente não são compatíveis com musl, então o Alpine está fora de questão; o requisito é glibc, e o
.NET 10 não publica imagem Debian — por isso a base é Ubuntu, e não a Debian-slim que versões anteriores
usavam. Nada do que você configura muda com isso: os mesmos nomes de pacote, o mesmo UID 1654 sem
privilégios, os mesmos caminhos. A imagem já traz ferramentas PKCS#11 genéricas
(`libpcsclite1` + `opensc`); os drivers de HSM dos fabricantes (SafeNet, Thales, Entrust, Yubico) são
montados pelo operador em tempo de execução, via `volumes:` no arquivo do compose. Veja os
exemplos comentados em `deploy/docker/docker-compose.yml`.
Um `HEALTHCHECK` consulta `/api/health` a cada 30 segundos, de modo que `docker ps` e orquestradores
enxergam o status `(healthy)` / `(unhealthy)` corretamente.
Bind mounts e caminhos no host:
| Caminho no container | Caminho no host | Finalidade |
|----------------------|-----------------|------------|
| `/app/appsettings.Production.json` | `./config/appsettings.Production.json` (somente leitura) | Configuração editada pelo operador |
| `/var/lib/bulksigner` | `./data` | Árvore de dados operacionais (input / processing / output / db) |
| `/var/log/bulksigner` | `./logs` | Arquivos de log duráveis |
Se você usar um [logotipo do cliente](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#branding--o-logotipo-do-cliente-nas-páginas-de-login-e-de-aprovação)
nas páginas de login e de aprovação, ele é montado da mesma forma — somente leitura, com
`Branding:CustomerLogo:Path` indicando o caminho *dentro* do container. O arquivo do compose traz o
exemplo comentado.
## Console em primeiro plano (pontual / teste)
Execute o executável publicado diretamente para iniciar o serviço em primeiro plano — útil para um
teste local rápido ou para ver erros de bootstrap imediatamente:
```bash
# Linux
./publish/Lacuna.BulkSigner
# Windows
.\publish\Lacuna.BulkSigner.exe
```
- A árvore `data/` é criada em relação ao diretório de trabalho.
- Use `Ctrl+C` para parar. O banner de bootstrap imprime `host mode = console`.
- Em um terminal interativo, um painel de status ao vivo substitui o log em fluxo contínuo. Veja
[Dashboard no console](https://docs.lacunasoftware.com/articles/bulk-signer/dashboard#dashboard-no-console-somente-execuções-em-primeiro-plano).
## Armazenamento em Azure Files (opcional)
Todas as instalações acima mantêm a árvore `data/` inteira no host. Definir
[`Storage:Provider = AzureFiles`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#storageprovider--storageazurefiles--o-compartilhamento-de-trabalho)
move o **compartilhamento de trabalho** — `processing/`, `output/` e `error/` — para um
compartilhamento do Azure Files, acessado pelo SDK do próprio serviço. Sem montagem SMB, sem
dependência no nível do host, sem mudança em nenhum passo de instalação acima.
O que você decide antes de instalar:
| Decisão | Observações |
|---------|-------------|
| **Qual compartilhamento abriga as raízes de trabalho** | Um compartilhamento, não vários: promover um artefato verificado e realocar a cópia em staging de um job que falhou são operações de *rename*, e o rename do Azure não funciona entre compartilhamentos nem entre contas. Acrescente um prefixo `Directory` se várias implantações dividirem um mesmo compartilhamento. |
| **Onde ficam as pastas de entrada** | Independente do item acima e definido por pasta — a passagem de uma pasta de entrada para o staging é uma *cópia*, e uma cópia pode cruzar qualquer fronteira. Uma pasta pode continuar local enquanto o compartilhamento de trabalho é remoto, ou ler o compartilhamento de um cliente na conta dele. |
| **A credencial** | `ManagedIdentity` (atribuída pelo sistema, sem segredo) onde o host roda no Azure; `ServicePrincipal` *on-premises*; `AccountKey` apenas onde o host não consegue alcançar o tenant de forma alguma. Veja [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security#credenciais-de-armazenamento-do-azure-files). |
| **O intervalo de polling por pasta remota** | O Azure Files não publica notificações de mudança, então uma pasta remota é **enumerada por temporizador**. Uma pasta para a qual nenhum intervalo se aplica é recusada no boot. |
Os requisitos do host são os mesmos em todos os alvos: HTTPS de saída para
`https://.file.core.windows.net` e — para `ManagedIdentity` — um endpoint IMDS alcançável, o
que no alvo Docker significa que o container precisa alcançar `169.254.169.254`.
> **Aviso: logs/ e o banco SQLite nunca podem ir para um compartilhamento**
>
> `Logging:File:Path` é sempre um caminho local, e a inicialização **recusa** uma configuração que o
> mova: o destino de arquivo acessa o sistema de arquivos pela própria API e não pode apontar para um
> compartilhamento. Com `Database:Provider = Sqlite`, uma `ConnectionStrings:Default` que aponte para um
> compartilhamento é recusada, porque acessar um arquivo de banco de dados por SMB é a forma documentada de
> corrompê-lo. Com `SqlServer`, o diretório `db/` simplesmente não é usado.
**O que muda operacionalmente.** A entrada de arquivos deixa de ser orientada a eventos e passa a ser
por temporizador (cerca de meio minuto nos padrões, no pior caso), inspecionar `error/` e
`processing/` passa a exigir um cliente de armazenamento em vez de uma sessão SSH, e o
compartilhamento é marcado no boot, de modo que uma segunda instância fica visível. Os três pontos
estão detalhados em
[Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#o-que-muda-no-dia-a-dia-em-um-compartilhamento).
**Verificação.** O banner de boot ganha as linhas `work share`, `azure credential`,
`input providers` e `azure shares = N reachable`; o `/api/ready` ganha uma verificação
`storage-share:/` por compartilhamento, mais uma linha `work-share-owner`.
Coloque um arquivo em uma pasta de entrada remota e veja-o aparecer como job em até um intervalo de
polling.
## O arquivo de certificado no Azure Blob Storage (opcional)
Um host sem disco local durável também não tem onde guardar o `.pfx` ou o `.cer`. As duas origens que
apontam para um arquivo podem, em vez disso, apontar para um blob — veja
[Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#lendo-o-arquivo-de-um-blob). Duas coisas para planejar na instalação:
- A identidade precisa de **Storage Blob Data Reader** no container, e de nada mais amplo.
- **Um blob inalcançável deixa aquele perfil degradado, mas o host continua rodando.** O perfil é
identificado no banner de inicialização, no log e no `/api/ready`, jobs roteados para ele falham com `profile.degraded`, e todos
os outros perfis continuam assinando. Corrija o acesso e reinicie. (Até a 2.0.x, um blob inalcançável
impedia o host de iniciar.)
## Escolhendo onde fica o banco operacional
Todas as instalações acima colocaram o **banco operacional** — jobs, o histórico deles, eventos
operacionais, a flag de pausa do pipeline, as regras de aprovação congeladas e as aprovações
registradas — em um arquivo SQLite em `Storage:Root`. Esse é o padrão, continua sendo o padrão, e uma
implantação que não configura nada o mantém.
| Escolha | Quando |
|---------|--------|
| **`Sqlite`** (padrão) | Qualquer host com disco local durável. Sem dependência externa, nada a provisionar, e adequado para uma instalação isolada da rede. |
| **`SqlServer`** | Sua política exige dados operacionais no seu próprio SGBD, sob o seu regime de backup, HA e DR — as aprovações registradas, em particular, são a evidência que um auditor pede. Ou o host **não tem disco local durável**; nesse caso, um arquivo SQLite não é um registro de verdade, e sim um registro que desaparece na próxima revisão. |
> **Info: Nenhum dos dois é uma decisão de vazão**
>
> O SQLite não é o teto deste pipeline — o limite é criptografia e I/O — e nada no `SqlServer` torna a
> assinatura mais rápida. Isso importa porque "migramos para o SQL Server" soa como uma decisão de
> escalabilidade, e leva à conclusão errada sobre instâncias: **escolher `SqlServer` não torna, por si só,
> uma segunda instância suportada.** Rodar mais de uma instância é uma opção explícita (`Cluster:Enabled`),
> com pré-requisitos e lista de limites próprios — o `SqlServer` é um desses pré-requisitos, e não o
> único. Sem essa opção ligada, duas instâncias sobre um mesmo compartilhamento de trabalho continuam
> sendo um risco documentado: veja
> [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quando-outra-instância-parece-ser-dona-do-compartilhamento-de-trabalho). Para
> escalar horizontalmente de fato, comece em
> [Alta disponibilidade e seus limites](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability).
### O que ter pronto antes do primeiro boot
1. **O banco de dados, criado.** O Bulk Signer cria suas *tabelas*, não seu banco de dados. O probe de
boot abre uma conexão com o banco indicado na connection string, então um banco inexistente é
interpretado como banco inalcançável e a migração é pulada — o serviço inicia, e o `/api/ready` fica
vermelho.
2. **Um login mapeado para um usuário nele**, em `db_datareader` + `db_datawriter` + `db_ddladmin` —
não `db_owner`. O script `ALTER ROLE` está em
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#antes-de-apontá-lo-para-o-sql-server).
3. **Conectividade de rede e um TLS que o host aceite.** O cliente SQL criptografa por padrão, então um
servidor *on-premises* cujo certificado não seja confiável para o host recusa o login com um erro
*certificate chain … not trusted*.
O Azure SQL também precisa de uma regra de firewall no servidor (ou de um private endpoint / regra de
VNet) para este host, além das portas de saída que a sua **connection policy** exige — o pré-requisito
mais fácil de esquecer, porque a política padrão não é uma só:
| Onde o host roda | Política padrão | Portas de saída a liberar |
|------------------|-----------------|---------------------------|
| **Dentro do Azure** (VM, VMSS, container app, App Service) | `Redirect` | TCP **1433** para o gateway **e TCP 11000–11999** para os endereços SQL da região. Use a service tag `Sql.` em um NSG em vez de enumerar IPs. |
| **Fora do Azure** (host *on-premises* acessando o Azure SQL) | `Proxy` | Apenas TCP **1433**. |
Liberar somente a 1433 de dentro do Azure é suficiente para estabelecer a sessão TCP e insuficiente
para usá-la, e é exatamente esse tipo de falha que esta lista existe para evitar.
### Por alvo — onde fica a connection string
A connection string pode conter a credencial inteira, então deve ficar onde aquele alvo já guarda
segredos — nos mesmos lugares, e pelos mesmos motivos, que a licença do PKI.
| Alvo | Onde colocar `ConnectionStrings__Default` | Opção sem senha |
|------|-------------------------------------------|-----------------|
| **systemd no Linux** | `/etc/bulksigner/bulksigner.env` (`0640`, de propriedade de `bulksigner`). Ou `appsettings.Production.json` quando não contiver senha. | Em uma VM ou VMSS do Azure: `Authentication=Active Directory Managed Identity`. Não em hosts *on-premises* — nesse caso, use um login SQL. |
| **Serviço do Windows** | Variável de ambiente de escopo de máquina, definida do mesmo jeito que a licença do PKI. | Autenticação integrada do Windows. O serviço roda como `NT SERVICE\LacunaBulkSigner`, que acessa a rede como a **conta de computador**, então o login a criar é `DOMINIO\HOSTNAME$`. Rode o serviço como uma gMSA ou um usuário de domínio para ter uma identidade por serviço. |
| **Docker / Compose** | `deploy/docker/.env`. | Somente onde o container consegue alcançar o endpoint IMDS do host (`169.254.169.254`). Diferentemente do provider do Azure Files, uma identidade **atribuída pelo usuário** também funciona aqui (`User Id=`). |
| **Console (dev)** | `appsettings.Development.json` ou uma variável de ambiente de shell comum. | `Authentication=Active Directory Default` usa o seu próprio `az login` — prático localmente, mas não é o que você quer em produção. |
### Migrando do SQLite — arquive o arquivo antigo primeiro
Definir `Database:Provider = SqlServer` em uma instalação existente resulta em um **banco vazio**. Não
há importador nem verificação no boot para o arquivo deixado para trás: sem jobs, sem histórico, sem
eventos operacionais e **sem snapshots de aprovação e sem aprovações registradas**.
Na ordem:
1. **Drene o pipeline** — pause-o, espere a contagem de jobs em andamento chegar a zero e então pare o
serviço. Antes disso, libere ou rejeite qualquer job retido na etapa de aprovação: ele não existe no
novo banco, e as decisões dos aprovadores estão no arquivo que você está prestes a arquivar.
2. Copie `db/bulksigner.db` para algum lugar coberto pela sua política de retenção e mantenha um
cliente SQLite à mão. A partir daí, ele é o seu arquivo morto, e não mais o do serviço.
3. Crie o banco de dados e o login, defina `Database:Provider` e `ConnectionStrings:Default`, inicie o
serviço e verifique conforme descrito abaixo.
Arquivos em `input/`, `output/` e `error/` não são tocados pela migração — mas a recuperação de
inicialização reconcilia `processing/` **a partir das linhas de job**, e o novo banco não tem nenhuma.
Inspecione e limpe manualmente qualquer resíduo, comparando com o banco arquivado, antes do primeiro boot
no novo banco.
### Verificando um banco operacional no SQL Server
Confira o banner de boot, que identifica o banco operacional em **toda** implantação:
```text
operational store = SQL Server (sqlsrv01/BulkSigner)
store status = reachable
```
Em uma instalação local, o banner mostra `operational store = SQLite (data/db/bulksigner.db)` e nenhuma
das outras duas linhas — nenhum probe foi feito. Nenhuma das linhas jamais exibe a connection string.
Duas linhas exigem ação:
- **`store status = UNREACHABLE: …`** — o banco não respondeu. O host iniciou mesmo assim, de
propósito (um banco de dados fora do ar durante uma janela de manutenção não pode transformar uma
reinicialização em indisponibilidade), a migração foi pulada, e o `/api/ready` está vermelho.
Corrija o banco e **reinicie**.
- **`store isolation = READ_COMMITTED_SNAPSHOT off …`** — a única configuração que faz o produto
parecer quebrado sem falhar em nada: as leituras do dashboard ficam bloqueadas atrás das gravações do
pipeline. O Azure SQL a habilita por padrão; o SQL Server *on-premises*, não. O Bulk Signer informa essa
configuração e **nunca a altera** — o comando precisa de acesso exclusivo a um banco de dados que
pertence a você. Peça a um DBA um `ALTER DATABASE` e depois reinicie.
Depois, `curl -H "X-API-Key: …" http://localhost:8080/api/ready/details` — a verificação `database`
identifica o banco que de fato foi verificado. (O `/api/ready` sozinho informa que a verificação está
verde; o nome do banco fica na rota de detalhes, protegida pela chave.)
## Login pelo Microsoft Entra ID (opcional)
Por padrão, o login do dashboard usa a chave de API, e nada aqui é necessário — uma implantação isolada
da rede nunca acessa um tenant da Microsoft. Para permitir que as pessoas entrem com as contas Microsoft
Entra ID da organização, em vez disso:
> **Dica: Alternativa com script**
>
> O `New-BulkSignerEntraApp.ps1` (que acompanha o pacote de implantação, veja [Exemplos](https://docs.lacunasoftware.com/articles/bulk-signer/samples))
> executa os passos 1, 2 e 4 por meio do Microsoft Graph, cria o client secret e imprime o bloco exato de
> configuração do passo 5. Apenas o passo 3 permanece manual.
>
> ```bash
> pwsh New-BulkSignerEntraApp.ps1 -BaseUrl https://signer.example.com
> ```
**1. Registre a aplicação** no tenant (Entra admin center → App registrations → New):
- **Supported account types:** *Accounts in this organizational directory only* — tenant único; a
aplicação recusa no boot os pseudo-tenants multi-tenant.
- **Redirect URI:** tipo *Web*, valor `https:///signin-oidc`.
- Em **Certificates & secrets**, crie um **client secret** e copie o valor imediatamente — ele é
exibido uma única vez.
**2. Crie as duas funções de aplicativo (app roles)** (App registration → App roles → Create):
| Display name | Value (precisa coincidir exatamente) | Allowed member types | Concede |
|------------------|--------------------------------------|----------------------------|---------|
| Administrator | `Administrator` | Users/Groups | O dashboard do operador — todas as páginas e ações que o cookie da chave de API concede hoje. |
| Approver | `Approver` | Users/Groups | As telas de aprovação. Os arquivos de pagamento sobre os quais a pessoa pode decidir continuam definidos pelo pool de aprovadores congelado, associado por e-mail — a função apenas abre a porta. |
**3. Atribua as pessoas** (Enterprise application → Users and groups → Add). Uma mesma pessoa pode ter
as duas funções e, nesse caso, é de fato as duas coisas. Uma conta **sem nenhuma das funções é
recusada** pela aplicação, mesmo quando se autentica.
**4. Exija a atribuição** (Enterprise application → Properties → **Assignment required = Yes**), para que
contas não atribuídas sejam barradas já na porta da Microsoft. A aplicação exige a função de qualquer
forma — a configuração do tenant nunca pode ser a única barreira.
**5. Configure o host** — veja
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#authentraid--login-opcional-pelo-microsoft-entra-id):
```bash
Auth__EntraId__TenantId=
Auth__EntraId__ClientId=
Auth__EntraId__ClientSecret= # variável de ambiente recomendada; nunca versione
```
Reinicie. O `/login` passa a oferecer **Entrar com a Microsoft**, e o formulário de chave de API é
desativado — uma seção preenchida pela metade impede a inicialização, com uma mensagem que cita a chave
que falta. Aprovadores que correspondem a um pool pelo e-mail do diretório são levados a `/approvals`; os
links duráveis de aprovador continuam funcionando para pessoas fora do tenant.
> **Aviso: O e-mail importa para os aprovadores**
>
> A correspondência com o pool usa a claim de e-mail do token. Para contas de convidado — aprovadores
> externos convidados para o tenant —, confira se o **atributo mail** da conta contém o endereço
> corporativo configurado no pool. O UPN modificado com `#EXT#` não é usado como alternativa, de
> propósito.
> **Perigo: Ativar isso desconecta todo mundo**
>
> Os cookies de operador existentes deixam de atender às políticas imediatamente — não sobra um período de
> oito horas de sessões criadas por um formulário de login que não existe mais. Planeje a transição
> levando isso em conta. Clientes REST que usam `X-API-Key` não são afetados.
## Atualizações
O schema do banco de dados é migrado automaticamente na inicialização. Primeiro, baixe a nova versão —
pelos mesmos dois canais da primeira instalação, [Obtendo o produto](https://docs.lacunasoftware.com/articles/bulk-signer/installation#obtendo-o-produto) — e use o pacote
de implantação que veio com ela, e não a cópia usada na instalação anterior. Depois, para atualizar no
lugar:
| Alvo | Passos |
|------|--------|
| Linux | Extraia o novo [arquivo de binários](https://docs.lacunasoftware.com/articles/bulk-signer/installation#os-binários-publicados) e então execute `sudo bash deploy/linux/install.sh --from ` — o script para a unit, reimplanta o binário e reinicia. |
| Windows | Extraia o novo [arquivo de binários](https://docs.lacunasoftware.com/articles/bulk-signer/installation#os-binários-publicados) e então execute `.\deploy\windows\Install-Service.ps1 -From ` — o script para o serviço, espelha a árvore de binários e reinicia. |
| Docker | Atualize a tag na linha `image:` do arquivo do compose e então execute `docker compose pull && docker compose up -d`. Antes, rode o `docker login` novamente se o token de acesso tiver expirado desde a instalação. |
> **Aviso: Sempre faça backup do banco de dados operacional antes de atualizar.**
>
> Com `Database:Provider = Sqlite`:
>
> | Alvo | Comando de backup |
> |------|-------------------|
> | Linux | `sudo cp /var/lib/bulksigner/db/bulksigner.db /var/lib/bulksigner/db/bulksigner.db.bak` |
> | Windows | `Copy-Item C:\ProgramData\Lacuna\BulkSigner\data\db\bulksigner.db -Dest .\bulksigner.db.bak` |
> | Docker | `cp deploy/docker/data/db/bulksigner.db deploy/docker/data/db/bulksigner.db.bak` |
>
> Com `SqlServer`, o backup fica a cargo do regime do seu SGBD — uma das duas razões pelas quais um
> cliente escolhe esse provider. O `db_ddladmin` precisa estar concedido no boot que aplica a migração; um
> boot com um schema já atualizado não cria nada.
A varredura de recuperação na inicialização separa automaticamente qualquer job deixado em andamento
pela versão anterior — não é necessária nenhuma limpeza manual. Veja
[Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#recuperação-na-inicialização).
### Atualizando para a 2.0.0
Quatro mudanças podem impedir de subir uma implantação que hoje sobe normalmente, ou alterar o que um
script existente recebe. Todas são deliberadas; a primeira é a que você deve verificar *antes* de editar
qualquer coisa.
- **Uma pasta de entrada monitorada que aponta para uma raiz de trabalho agora impede o boot.** Uma
configuração assim reprocessava e depois apagava os artefatos que ela mesma produzia, um por iteração,
enquanto informava todo job como `Completed`. **Compare o `output/` com o que os destinatários de fato
coletaram** antes de corrigir a configuração — veja
[Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#uma-implantação-que-antes-iniciava-agora-é-recusada-citando-uma-pasta-de-entrada-monitorada).
- **A ação Limpar Jobs apaga apenas registros finalizados.** O `DELETE /api/jobs` agora retorna
`skipped` junto com `deleted`, e um script que limpa a tabela e depois espera que ela esteja vazia
precisa, antes, drenar ou cancelar os jobs não finalizados. Veja [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#limpar-jobs). *Revertido na
2.9.0 — veja [abaixo](https://docs.lacunasoftware.com/articles/bulk-signer/installation#atualizando-dentro-da-2x).*
- **Um arquivo cujo caminho excede 850 caracteres é recusado no momento da entrada**, com o novo código
de problema `job.path-too-long`, em vez de ser aceito e falhar depois.
- **O card "Vazão máxima/s" do dashboard foi removido.** O histograma
`bulksigner_signing_duration_seconds` em `/api/metrics` não mudou e continua sendo o registro
externo. Veja [Estatísticas de jobs](https://docs.lacunasoftware.com/articles/bulk-signer/statistics#a-vazão-máximas-acabou).
Esta versão adiciona migrações em **ambos** os históricos de banco de dados, aplicadas no boot — de
modo que o aviso de backup acima importa mais do que o normal nesta atualização.
> **Aviso: Vai ativar o modo cluster? Suba uma vez com ele desligado primeiro**
>
> `Cluster:Enabled = true` restringe a recuperação de inicialização aos jobs de cada instância, e uma
> linha deixada em andamento por um build mais antigo **não tem dono** — então, com o modo ligado, nada
> jamais vai recuperá-la. Suba uma vez com `Cluster:Enabled = false`, deixe a recuperação rodar e só então
> ative o modo. É uma preocupação única, no momento da atualização. Veja
> [Azure App Service](https://docs.lacunasoftware.com/articles/bulk-signer/azure#6-primeiro-boot-em-uma-instância).
### Atualizando dentro da 2.x
Toda versão 2.x pode ser atualizada in-place com os passos acima, e você pode ir de qualquer 2.x direto
para a mais recente — as migrações das versões intermediárias são aplicadas em ordem no primeiro boot. As
versões 2.1.0, 2.2.0, 2.4.3, 2.5.0, 2.7.0, 2.13.0, 2.14.0, 2.15.0, 2.16.0, 2.17.0 e 2.18.0 adicionam
migrações; faça o backup acima antes de qualquer uma delas. As versões abaixo também exigem alguma ação
sua e estão listadas na ordem em que você passaria por elas:
- **2.1.0 — os perfis de assinatura passam para o banco operacional.** O primeiro boot na 2.1.0 ou
posterior importa a sua seção `Signing:Profiles[]` (ou deriva um perfil `default` do
`Signing:Certificate`) **uma única vez**; depois disso, a seção é ignorada, o log de inicialização avisa
isso a cada boot até você removê-la, e os perfis passam a ser criados e editados pelo dashboard. **Se
algum perfil tiver um segredo** — uma senha de PFX, um segredo de aplicativo do Key Vault, uma
credencial de blob —, defina `Signing__ProfileSecretsKey` antes desse boot, ou a importação será
recusada, com uma mensagem que cita a chave; faça backup da chave junto com os seus outros segredos. Veja
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofilesecretskey--a-chave-que-criptografa-os-segredos-dos-perfis-armazenados).
Mais três mudanças chegam na mesma versão:
- Um **bloco `Signer:` incompleto agora impede o boot** mesmo quando nenhum perfil usa o
Lacuna Signer — complete-o ou remova-o.
- Um **certificado que não abre não impede mais o host de iniciar**: aquele perfil fica degradado, a
linha dele no `/api/ready` informa `ok: false` sem transformar a resposta em `503`, e os jobs dele
falham com `profile.degraded`. Um alerta que lê só o `ready` de nível superior não percebe isso —
leia o `checks[]`.
- Um arquivo que um aprovador **rejeita** é devolvido ao `output/` com `.reject` no nome, e o original
é removido da pasta monitorada. Qualquer processo que trate todo arquivo do `output/` como assinado
agora precisa ler o nome. Se você ligou o segundo fator do aprovador definindo só o
`ApproverSecondFactor:SeedSecret`, defina também `ApproverSecondFactor__Enabled=true` explicitamente
— o padrão de fábrica é desligado.
- **2.2.0 — o perfil escolhe sua pasta monitorada.** O vínculo da pasta sai do
`Storage:Inputs[].Profile` e vai para o perfil. Na atualização a partir da 2.0.x, a importação do
primeiro boot vincula cada pasta com base nessa chave, como antes. Na atualização a partir da
**2.1.x**, os perfis já foram importados sem vínculo, então **toda pasta monitorada sobe sem
atribuição** e os arquivos dela ficam esperando, sem assinatura: escolha cada pasta na página do
respectivo perfil (*Editar comportamento* → **Pasta de entrada**) logo depois da atualização.
- **2.2.1 — saia e entre de novo uma vez.** O dashboard agora leva a identidade do operador conectado
para dentro de cada página, e uma sessão aberta durante a atualização mantém o ticket antigo até ser
renovada. Com o Entra ID, os eventos de auditoria dos operadores passam a ser registrados com o UPN
deles, em vez de `(anonymous)`.
- **2.3.1 — o `Auth:ApiKey` precisa ser o seu.** O `appsettings.json` distribuído não traz mais uma
chave de exemplo, perfis de exemplo nem um bloco `Signer`, e o `Pipeline:MaxConcurrency` desse arquivo
voltou ao padrão do produto, `1`. Uma implantação que nunca definiu `Auth__ApiKey` agora se recusa a
iniciar, citando a chave; uma que dependia da concorrência antiga precisa definir ela mesma o
`Pipeline__MaxConcurrency`. Um primeiro boot recusado citando perfis que você nunca declarou era esse
defeito — use a 2.3.1 ou posterior.
- **2.4.1 — Docker: pule as imagens 2.3.2 e 2.4.0.** Essas duas imagens de container não traziam o
script cliente do dashboard: as páginas eram renderizadas, mas nenhum controle funcionava, enquanto o
`/api/ready` continuava verde. Faça o pull da 2.4.1 ou posterior. Instalações como serviço nunca foram
afetadas.
- **2.4.3 — pausar e retomar no SQL Server.** Um banco operacional SQL Server criado antes desta versão
nunca recebeu a linha que guarda a flag de pausa, então pausar retornava `pipeline.state-missing`; a
migração acrescenta essa linha.
- **2.5.0 — reimplantações de cluster no App Service.** Um container novo agora desloca aquele que ele
substitui, em vez de ser recusado. As atualizações continuam exigindo parada total; veja
[Azure App Service](https://docs.lacunasoftware.com/articles/bulk-signer/azure#8-atualizações-exigem-parada-total).
- **2.6.0 — o `/api/ready` perde o detalhe.** O probe anônimo agora traz só o nome e o veredito de cada
verificação; um monitoramento que interpretava o `detail` deve passar a usar o `/api/ready/details` e
enviar `X-API-Key`. O código de status não muda. Veja [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#readiness).
- **2.7.0 — uma métrica muda de identidade.** A `bulksigner_approver_signatures_total` ganha um label
`means` (`browser` / `cloud`), então a identidade das séries dela muda para qualquer ferramenta que a colete.
- **2.9.0 e 2.10.0 — a ação Limpar Jobs apaga tudo.** Ela agora apaga todo job, qualquer que seja o status, os
arquivos que esses jobs deixaram e (desde a 2.10.0) todo evento operacional registrado antes da
limpeza, gravando um evento `JobsCleared` como registro dela. A resposta perde o `skipped` e ganha
`filesDeleted`, `foldersDeleted`, `itemsFailed` e `eventsDeleted`. Um script escrito contra o
comportamento da 2.0.0 acima precisa mudar. Veja [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#limpar-jobs).
- **2.13.0 — um nome de arquivo já processado é recusado.** Um arquivo que chega com o nome de um job
concluído ou ainda ativo agora falha com `file.already-processed`, em vez de ser assinado de novo. Um
sistema produtor que reutiliza o mesmo nome de arquivo fixo todo dia precisa de
`Pipeline__RejectAlreadyProcessedFileNames=false` **antes** da atualização. Veja
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#pipeline).
- **2.16.0 — uma indisponibilidade do Lacuna Signer agora consome o limite do polling.** Nada muda para
um host sem perfil `Method = LacunaSigner`. Com um, um `5xx`, `408` ou `429` do Signer, a página de
erro de um proxy ou um timeout durante um polling ou download agora conta para o
`Signer:MaxConsecutiveApiFailures` — antes, era tentado de novo sem contar até o `Signer:TimeoutHours`
—; assim, uma indisponibilidade mais longa que o limite faz os jobs em espera falharem com
`signer.unreachable`, para uma nova tentativa quando o Signer voltar. O `originalPath` de um job agora
pode ser `null` no `GET /api/jobs` (só para os jobs do novo método `SignerFolder`, que não têm
arquivo); um cliente que espera uma string deve tolerar isso. Veja
[Falhas de API](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#falhas-de-api-e-o-limite-por-job).
## Verificações rápidas de saúde
Depois de instalar em qualquer alvo:
| URL | O que ela informa |
|-----|-------------------|
| `http://localhost:8080/api/health` | Liveness — anônimo, retorna `200 OK` se o processo do host está no ar. |
| `http://localhost:8080/api/ready` | Readiness — anônimo por padrão, retorna um corpo que lista cada probe (banco operacional, cada pasta de entrada, licença, além das linhas `storage-share:` e `work-share-owner` em um compartilhamento de trabalho remoto) com o respectivo veredito, sem detalhes. `503` se algum probe que conta para o veredito falhou. |
| `http://localhost:8080/api/ready/details` | O mesmo relatório com o detalhe de cada probe. Exige a chave de API. |
| `http://localhost:8080/` | O dashboard do operador. Entre com a chave de API de `Auth:ApiKey` — ou com a Microsoft, quando o [login pelo Entra ID](https://docs.lacunasoftware.com/articles/bulk-signer/installation#login-pelo-microsoft-entra-id-opcional) estiver configurado. |
| `http://localhost:8080/scalar/v1` | A referência OpenAPI interativa da API REST. |
O `/api/health` é sempre anônimo, para que ferramentas externas de health check não precisem de
credenciais. O `/api/ready` é anônimo por padrão pelo mesmo motivo e não traz detalhes; o
`Readiness:RequireApiKey` o protege quando quem faz o probe consegue enviar um header. O
`/api/ready/details` e o `/api/metrics` são protegidos pela chave de API — veja [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security).
---
**A seguir:** [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration) — o que cada chave do `appsettings.json` faz.
# [Implantando no Azure App Service — modo cluster](https://docs.lacunasoftware.com/articles/bulk-signer/azure)
Do zero a um cluster de duas instâncias, verificado na visão **Instâncias** do dashboard.
Este é o passo a passo da **única topologia com múltiplas instâncias suportada**: um Azure Web App
executando a imagem de container Linux, com escala horizontal em um único App Service Plan. Os
mecanismos se coordenam por meio do banco de dados operacional e do compartilhamento de trabalho e
independem do host, mas a topologia *suportada* é exatamente esta — duas VMs on-premises usando um
mesmo SQL Server rodariam o mesmo código, mas não são documentadas, testadas nem suportadas.
> **Perigo: Leia os limites antes de começar**
>
> **[Alta disponibilidade e seus limites](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability)** lista o que esta topologia não
> oferece — atualizações que exigem parada total, ausência de deployment slots, cotas de limite de
> requisições (rate limiting) que se multiplicam, coleta de métricas que cai em uma instância arbitrária.
> É melhor conhecer cada item agora do que descobri-lo numa janela de mudança.
Instalações de instância única ficam em outra página e não são afetadas: systemd no Linux, Windows
Service, Docker e o console em primeiro plano estão em [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation), e **nada nesta
página se aplica a eles**. `Cluster:Enabled` tem padrão `false`, e, com ele desligado, o produto é,
byte a byte, o de instância única.
## Visão geral da topologia

Toda seta é uma chamada HTTPS de saída na porta 443, inclusive a do compartilhamento: este produto
alcança o Azure Files pelo SDK de armazenamento e **nunca por SMB**, então não existe porta 445 em
lugar nenhum do diagrama, nem unit de montagem, nem pacote no host.
Leia juntas as duas setas que chegam ao Key Vault, porque elas são a única assimetria da página. Seis
permissões pertencem à **identidade gerenciada** do web app; exatamente uma pertence a um **registro de
aplicativo do Entra** — o direito de assinar com a chave — e é a única credencial aqui que é um segredo
que alguém precisa rotacionar. O [passo 3](https://docs.lacunasoftware.com/articles/bulk-signer/azure#3-a-chave-de-assinatura-fica-em-um-cofre) explica de onde
isso vem, e por quê.
## Antes de começar
Seis coisas precisam estar resolvidas antes do primeiro boot. Se faltar alguma das quatro primeiras, o
resultado é uma recusa de boot, um erro fatal ou uma linha de readiness vermelha, e não uma surpresa em
tempo de execução; as duas últimas são recomendações que esta topologia torna muito mais fortes do que
em outros cenários.
| # | O quê | Por que o modo cluster exige |
|---|---|---|
| 1 | **Um banco de dados Azure SQL**, mais um login em `db_datareader` + `db_datawriter` + `db_ddladmin` | O banco operacional é o ponto de coordenação do cluster: as reivindicações de job, a flag de pausa, a tabela de heartbeat e o key ring de sessão ficam todos ali. `Cluster:Enabled = true` com `Database:Provider` diferente de `SqlServer` é **recusado no boot**. Este serviço cria suas tabelas, não seu banco de dados. |
| 2 | **Um compartilhamento do Azure Files (SMB)** para a árvore de trabalho, com todo diretório de entrada monitorado criado dentro dele | `Storage:Provider` e **cada** entrada de `Storage:Inputs[]` precisam resolver para `AzureFiles`; caso contrário, o boot é recusado. O lease do banco local é um controle interno ao processo, que não exclui nada fora dele, e uma pasta local de uma instância é invisível para as demais. Um compartilhamento NFS é recusado explicitamente. |
| 3 | **Uma decisão entre as duas origens de certificado que não dependem do host** — `Pfx` lido de um blob, ou `AzureKeyVault` | `Pkcs11` e `WindowsStore` são **recusados no boot**: um token ou um repositório de máquina fica em uma única máquina, e as instâncias do cluster são intercambiáveis. Sobram duas, e nenhuma é a vencedora óbvia — o [passo 3](https://docs.lacunasoftware.com/articles/bulk-signer/azure#3-a-chave-de-assinatura-fica-em-um-cofre) apresenta a escolha e o que ela custa. Nada precisa existir ainda. |
| 4 | **A imagem de container em um registry do qual o web app consiga fazer pull** | A Lacuna a publica em seu repositório privado de imagens Docker; o [passo 1](https://docs.lacunasoftware.com/articles/bulk-signer/azure#1-importe-a-imagem) copia a tag que você recebeu para um registry seu, o que permite fazer o pull com identidade gerenciada. |
| 5 | **Uma tabela do Azure Storage para o destino de logs** | Fortemente recomendada, mas não obrigatória. O disco do container é efêmero, então arquivos de log rotacionados são descartados a cada reciclagem; deixar `Logging:AzureTable:Enabled = false` no modo cluster registra um **Critical na inicialização**, e o serviço sobe assim mesmo. Nada limpa essa tabela — leia [Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention#logs-em-uma-tabela--nada-os-poda) e agende o script de limpeza *antes* de ligar o destino. |
| 6 | **Um recurso do Application Insights** | Também recomendado, mas não obrigatório, por um motivo específico desta topologia: `/api/metrics` atrás do balanceador de carga alcança uma instância arbitrária, então a coleta pelo Prometheus não tem continuidade aqui. A distro do Application Insights distingue as instâncias nativamente e é o caminho de observabilidade recomendado para o cluster — veja [Telemetria](https://docs.lacunasoftware.com/articles/bulk-signer/telemetry). |
Habilite no web app uma **identidade gerenciada atribuída pelo sistema** antes de qualquer outra coisa,
porque assim quase tudo aqui consegue se autenticar sem segredo: `Credential = ManagedIdentity` para o
compartilhamento do Azure Files, `Authentication=Active Directory Managed Identity` na connection string
do SQL, e o mesmo modo para o blob do certificado e para a tabela de logs. Cada uma é uma permissão
separada, ainda que uma única identidade tenha todas — veja [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security).
> **Aviso: Uma credencial não pode ser uma identidade gerenciada**
>
> `Signing:…:Certificate:AzureKeyVault` exige `AppId` **e** `AppSecret`; o bloco não tem chave
> `Credential` nem modo de identidade gerenciada; por isso, com essa origem, o direito de assinar
> pertence a um registro de aplicativo do Entra com um client secret. Esse é o único segredo que esta
> topologia não consegue eliminar por design, e o [passo 3](https://docs.lacunasoftware.com/articles/bulk-signer/azure#3-a-chave-de-assinatura-fica-em-um-cofre) o
> compara com o que o cofre oferece. Vale saber disso antes de escolher uma origem de certificado.
## Dimensionando o plano e o banco de dados
Dois dos pré-requisitos acima não só precisam existir como também precisam ser dimensionados, e um
número que o operador geralmente conhece de antemão dimensiona ambos: assinaturas por dia. A tabela é
um **ponto de partida, não um resultado de benchmark** — o que ela não tem como saber são os seus
arquivos, e eles influenciam a resposta mais do que o volume. Uma assinatura CAdES sobre uma remessa
CNAB240 e uma assinatura PAdES sobre um PDF digitalizado de 300 páginas são, para este produto, um job
cada, mas não chegam nem perto de representar o mesmo trabalho.
| Assinaturas por dia | App Service Plan | Azure SQL — máx. de vCores |
|---|---|---|
| 0 – 1.000 | P0 V3 × 1 | 2 |
| 1.001 – 5.000 | P1 V3 × 1 | 4 |
| 5.001 – 20.000 | P1 V3 × 2 | 6 |
| 20.001 – 100.000 | P2 V3 × 2 | 8 |
| 100.001 – 1.000.000 | P2 V3 × 2 | 8 |
`× N` é o número de instâncias — `--number-of-workers` em **um** plano, nunca um segundo plano.
Qualquer que seja a sua linha, o passo 2 começa com um worker, e é no [passo 7](https://docs.lacunasoftware.com/articles/bulk-signer/azure#7-escale-para-duas)
que você escala até o número acima: é no primeiro boot que todas as recusas aparecem, e ler o console
de uma instância é mais fácil do que ler o de duas.
**As duas últimas linhas usam o mesmo hardware, e esse é o dado mais informativo da tabela.** Por
volta de 100.000 assinaturas por dia, a camada web deixa de ser a restrição. Um job é uma assinatura, e
o que a limita a partir daí é a origem do certificado — um `Pfx` já em memória, ou uma ida e volta ao
Key Vault — junto com o I/O do compartilhamento. Dez vezes o volume no mesmo plano indica, portanto,
onde o trabalho de fato está, e não uma folga que o plano mantinha de reserva. No topo dessa faixa,
dimensione primeiro
[a ida e volta ao cofre](https://docs.lacunasoftware.com/articles/bulk-signer/azure#a-latência-é-a-restrição-aqui-não-o-throttling) e
`Pipeline:MaxConcurrency`, e aumente o plano por último.
**Um SKU maior não assina mais arquivos ao mesmo tempo.** `Pipeline:MaxConcurrency` é a única coisa que
faz isso; o valor é por instância, e o [passo 3](https://docs.lacunasoftware.com/articles/bulk-signer/azure#3-a-chave-de-assinatura-fica-em-um-cofre) o define
como `4` — então uma linha `× 2` significa oito assinaturas em andamento, e é com oito, não com os
quatro configurados, que a origem do certificado precisa lidar. Em
[Alta disponibilidade](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability), essa mesma multiplicação aparece como limitação, e não
como funcionalidade.
**As duas linhas `× 1` não precisam desta página.** O modo cluster é válido com uma instância — o
passo 6 sobe exatamente assim —, mas, com uma instância, nada do que ele coordena está de fato
trabalhando, e uma implantação que permanece assim está pagando por um banco Azure SQL e um
compartilhamento do Azure Files de que não precisaria. Abaixo de 5.000 assinaturas por dia,
leia [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation) primeiro e volte quando o motivo for o volume, ou uma segunda
instância, e não a plataforma.
> **Observação: O que "máx. de vCores" significa, e por que o auto-pause nunca dispara**
>
> Um número de vCores *máximo* descreve a camada de computação **serverless** do Azure SQL, onde esse
> número é o teto até o qual o banco pode escalar (`--max-capacity`); na camada provisionada, leia o mesmo
> número como a quantidade de vCores, já que não há o que escalar. Em nenhum dos casos conte com a
> economia do auto-pause no orçamento. Este produto consulta o banco continuamente, e no modo cluster cada
> instância também grava um heartbeat a cada `Cluster:HeartbeatSeconds` — 15 por padrão —, de modo que o
> banco nunca fica ocioso nem perto da hora que o auto-pause exige. Dimensione-o como
> um banco de dados que está sempre no ar.
**Nada disso é um argumento de vazão a favor do `SqlServer`.** A coluna de vCores dimensiona um banco
cujo provider [Antes de começar](https://docs.lacunasoftware.com/articles/bulk-signer/azure#antes-de-começar) já tornou obrigatório aqui; não é um motivo para
migrar uma instalação de instância única do SQLite, que também não é o teto deste pipeline — essa
decisão é tratada em
[Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation#escolhendo-onde-fica-o-banco-operacional), com seus próprios critérios.
---
## 1. Importe a imagem
A Lacuna publica a imagem de container no seu **repositório privado de imagens Docker** e fornece a
você um usuário e um token de acesso a ele — veja
[Obtendo o produto](https://docs.lacunasoftware.com/articles/bulk-signer/installation#obtendo-o-produto). Nenhum build acontece aqui: você copia a tag
indicada para instalação para um container registry seu.
```bash
az group create --name bulksigner-rg --location brazilsouth
```
```bash
az acr create --name --resource-group bulksigner-rg --sku Basic
```
```bash
az acr import --name \
--source /bulksigner: \
--image bulksigner: \
--username --password
```
O `az acr import` é uma cópia de registry para registry feita pelo próprio serviço: as camadas nunca
passam pela sua estação de trabalho e nenhum daemon Docker local é envolvido. Pule qualquer uma das
linhas `create` se o resource group ou o registry já existirem — a importação é a parte indispensável, e
o `Basic` é suficiente para uma imagem baixada por um punhado de instâncias.
Importe uma tag `` explícita, e não `latest`. A tag que você indicar aqui é a que o passo 2 fixa
no web app, e nesta topologia uma atualização é uma
[janela de parada total](https://docs.lacunasoftware.com/articles/bulk-signer/azure#8-atualizações-exigem-parada-total) — algo que você agenda, e não algo que uma
reciclagem da plataforma impõe a você.
**Por que um registry seu, em vez de apontar o web app direto para o da Lacuna.** Dois motivos, e o
primeiro orienta o resto desta página: o app faz o pull com a sua **identidade gerenciada** (`AcrPull`,
passo 2), e uma identidade do seu tenant só pode receber uma função (role) em um registry do seu tenant
— um registry de terceiros exige um usuário e uma senha nos app settings, o que é mais um segredo a
rotacionar, e justamente o tipo de segredo que esta arquitetura se esforça para eliminar. O segundo é
disponibilidade: o App Service faz o pull da imagem de novo em toda instância que sobe, então a escala
horizontal ([passo 7](https://docs.lacunasoftware.com/articles/bulk-signer/azure#7-escale-para-duas)) e as reciclagens da plataforma passariam a depender de que um
registry fora da sua assinatura do Azure respondesse naquele momento.
## 2. Crie o plano e o app — uma instância primeiro
```bash
az appservice plan create --name bulksigner-plan --resource-group bulksigner-rg --is-linux --sku P1V3 --number-of-workers 1
```
O `P1V3` acima é o SKU do exemplo, não uma recomendação —
[Dimensionando o plano e o banco de dados](https://docs.lacunasoftware.com/articles/bulk-signer/azure#dimensionando-o-plano-e-o-banco-de-dados) indica qual deles
o seu volume exige.
**O Basic (B1) é o mínimo**, por ser a primeira camada que permite escala horizontal — e ele se limita
a três instâncias, o que também limita o que o Health check pode fazer por você, já que desviar o
tráfego de uma instância não saudável exige ter para onde desviá-lo. Para produção, a resposta é o
Premium v3. Comece deliberadamente com um worker: é no primeiro boot que todas as recusas aparecem, e
ler o console de uma instância é mais fácil do que ler o de duas.
```bash
az webapp create --name bulksigner --resource-group bulksigner-rg --plan bulksigner-plan --container-image-name .azurecr.io/bulksigner:
```
`bulksigner:` é o repositório e a tag que o [passo 1](https://docs.lacunasoftware.com/articles/bulk-signer/azure#1-importe-a-imagem) importou — no seu
registry, não no da Lacuna. Fixe essa tag em vez de `latest`, para que uma atualização seja uma ação
sua, e não o efeito colateral de um restart.
```bash
az webapp identity assign --name bulksigner --resource-group bulksigner-rg
```
Conceda a essa identidade `AcrPull` no registry, `Storage File Data Privileged Contributor` no
compartilhamento, um login SQL mapeado para ela em `db_datareader` + `db_datawriter` + `db_ddladmin`,
`Storage Blob Data Reader` no container que guarda o certificado, e `Storage Table Data Contributor` na
própria tabela de logs — restrita a essa tabela, que é tudo de que o destino precisa. O passo 3 adiciona
a sexta e última permissão.
> **Aviso: O que não entra nessa lista é o direito de assinar**
>
> Com `AzureKeyVault`, quem acessa a chave do cofre é o registro de aplicativo do Entra indicado em
> `AppId`, nunca esta identidade — então `get` + `sign` concedidos aqui iriam para a entidade de
> segurança (principal) errada, e o boot falharia mesmo assim, com uma mensagem sobre o cofre que não
> explica o motivo. A única permissão que esta identidade recebe nesse cofre é `Key Vault Secrets User`,
> para ler o próprio segredo do registro de aplicativo como app setting.
Depois, configure o app para fazer o pull com essa identidade em vez de uma senha de registry:
```bash
az resource update --ids $(az webapp config show --name bulksigner --resource-group bulksigner-rg --query id -o tsv) --set properties.acrUseManagedIdentityCreds=True
```
Sem essa última linha, o pull recorre a credenciais de registry que o app não tem, e o primeiro sintoma
é um container que nunca inicia — sem nada no log da aplicação, porque ainda não há aplicação rodando.
## 3. A chave de assinatura fica em um cofre
Duas origens passam pelas recusas de boot de [Antes de começar](https://docs.lacunasoftware.com/articles/bulk-signer/azure#antes-de-começar), e é neste passo que
você escolhe entre elas. Elas são realmente equivalentes — a escolha é de política, não técnica, e cada
uma abre mão de algo que a outra preserva.
| | `Pfx` lido de um blob | `AzureKeyVault` |
|---|---|---|
| Onde está a chave privada | Um `.pfx` no Blob Storage, carregado na memória da instância no boot | Dentro do cofre, permanentemente. Cada assinatura é uma chamada remota e a chave nunca deixa o Azure |
| Credencial | A identidade gerenciada do web app, `Storage Blob Data Reader`. **Sem segredo** | Um registro de aplicativo do Entra com um **client secret**, mais uma permissão da identidade gerenciada para ler esse segredo |
| Latência de assinatura | Nenhuma além da criptografia local | Uma ida e volta ao cofre por job |
| Se o Azure estiver inalcançável | Já assinou; o pipeline continua assinando | Todo job falha até o cofre responder |
| Auditoria | O histórico de jobs deste produto, e nada mais | Cada assinatura é uma operação registrada no Key Vault — uma contagem independente para conciliar com o histórico de jobs |
Resumindo com franqueza: o `AzureKeyVault` garante uma chave que não pode ser exfiltrada de uma
instância comprometida, e o preço é o único segredo que esta topologia não teria de outra forma. Se um
client secret nos app settings não for aceitável para você, **o `Pfx` como blob é uma resposta melhor do
que uma chave de cofre protegida por software** — uma chave em software é FIPS 140-2 Nível 1, o que
anula a maior parte do argumento a favor do cofre e mantém todo o seu custo. O restante desta seção
pressupõe que você escolheu o cofre; para `Pfx`, tudo o que você precisa está em
[Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#lendo-o-arquivo-de-um-blob), mais o upload do blob abaixo.
### Provisione o cofre, a chave e o registro de aplicativo
```bash
az keyvault create --name bulksigner-kv --resource-group bulksigner-rg --location brazilsouth --sku premium --enable-rbac-authorization true
```
**Premium, porque a chave deve ser protegida por HSM.** O Standard oferece uma chave em software, e o
parágrafo acima explica por que esse é o lado errado desta troca.
Um certificado ICP-Brasil é entregue como PFX, então a chave é *importada* em vez de gerada. O
[`Import-PfxToKeyVault.ps1`](https://docs.lacunasoftware.com/articles/bulk-signer/samples) faz toda a importação de uma só vez — grava o `.cer` público,
importa a parte privada como um objeto **key**, registra a aplicação no Entra, cria o segredo dela,
concede a ela `sign` e executa a mesma verificação de pareamento de chave pública que o serviço faz no
boot, de modo que uma divergência apareça aqui, e não no primeiro start:
```bash
pwsh Import-PfxToKeyVault.ps1 -PfxPath ./signer.pfx -VaultName bulksigner-kv -KeyName bulksigner-signing-key -AppDisplayName bulk-signer-prod -Destination HSM -GrantScope Key -SecretValidityYears 2
```
> **Observação: Um objeto key, não um objeto certificate — e essa diferença é o ponto central**
>
> O Key Vault guarda este PFX de qualquer forma, mas um *certificate* importado de um PFX é marcado como
> exportável: qualquer pessoa com `secrets/get` pode baixar o PFX completo do segredo associado a ele. Um
> objeto *key* nunca devolve material privado, qualquer que seja a permissão. É por isso que a origem
> deste produto usa somente a chave, e por isso o certificado público precisa ser fornecido separadamente.
O script imprime o client secret **uma única vez** e não o grava em lugar nenhum. Copie-o agora.
### Coloque o certificado onde uma instância consiga lê-lo
O script deixa o `.cer` na sua estação de trabalho, o que não serve para um container cujo disco é
reciclado junto com ele. Faça o upload e permita que a identidade gerenciada o leia:
```bash
az storage blob upload --account-name contosocerts --container-name certificates --name signer.cer --file ./signer.cer --auth-mode login
```
Conceda a essa identidade `Storage Blob Data Reader` no container — a quarta das permissões que o passo
2 listou. O `CerPath` continua disponível e aponta para um arquivo local, mas nesta topologia isso
significa embutir o certificado na imagem, o que amarra a renovação anual do certificado a um novo build
da imagem e a uma implantação com parada total. Use o blob.
### As configurações
São app settings como quaisquer outros, definidos aqui e não no passo 4 porque todos os valores foram
produzidos pelos dois comandos acima:
```bash
az webapp config appsettings set --name bulksigner --resource-group bulksigner-rg --settings \
Signing__Profiles__0__Certificate__Source=AzureKeyVault \
Signing__Profiles__0__Certificate__AzureKeyVault__Endpoint='https://bulksigner-kv.vault.azure.net/' \
Signing__Profiles__0__Certificate__AzureKeyVault__KeyName=bulksigner-signing-key \
Signing__Profiles__0__Certificate__AzureKeyVault__AppId=99990000-aaaa-bbbb-cccc-ddddeeeeffff \
Signing__Profiles__0__Certificate__AzureKeyVault__AppSecret='@Microsoft.KeyVault(VaultName=bulksigner-kv;SecretName=bulksigner-app-secret)' \
Signing__Profiles__0__Certificate__AzureKeyVault__Blob__Url='https://contosocerts.blob.core.windows.net/certificates/signer.cer' \
Signing__Profiles__0__Certificate__AzureKeyVault__Blob__Credential=ManagedIdentity \
Signing__ProfileSecretsKey='@Microsoft.KeyVault(VaultName=bulksigner-kv;SecretName=bulksigner-profile-secrets-key)' \
Pipeline__MaxConcurrency=4
```
O `0` é o índice do perfil em `Signing:Profiles[]` e é posicional — reordenar esse array faz estas
configurações apontarem silenciosamente para outro perfil. O `Endpoint` precisa ser uma URL `https://`
absoluta; um nome DNS puro é recusado no boot, com uma mensagem que cita a chave.
**As configurações `Signing__Profiles__0__*` são um seed (carga inicial) de uso único.** No primeiro
boot com uma tabela de perfis vazia, elas são importadas para o banco operacional como a linha do perfil;
depois disso, são ignoradas — o log de boot avisa isso toda vez que as encontra — e o perfil, incluindo o
certificado, passa a ser editado na página **Perfis de assinatura** do dashboard. Mantenha-as até o
primeiro boot ter rodado e então apague-as. Veja
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofiles--perfis-de-assinatura-por-pasta).
**O `Signing__ProfileSecretsKey` é a chave com a qual a importação criptografa o `AppSecret`**, porque o
banco nunca guarda um segredo em texto claro, e a importação é recusada sem ela. Crie-a uma vez como
segredo do cofre — qualquer valor aleatório longo, `openssl rand -base64 32`, por exemplo —, lida pelo
mesmo tipo de referência do Key Vault, e saiba que perdê-la equivale a perder todos os segredos de perfil
armazenados: não há como recuperá-los. Veja
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofilesecretskey--a-chave-que-criptografa-os-segredos-dos-perfis-armazenados).
Com `Pfx` como blob, a senha do PFX também é um segredo desse tipo; só um PFX sem senha lido com
`ManagedIdentity` dispensaria a chave.
> **Aviso: Blob e CerPath são mutuamente exclusivos — usar os dois é recusado no boot, e não usar nenhum também**
>
> Essa recusa é deliberada, e não um excesso de rigor: dois certificados válidos para uma mesma chave de
> cofre passariam na verificação de pareamento, então resolver o conflito por precedência faria com que
> *qual certificado assinou* dependesse das condições da rede, sem possibilidade de auditoria. Note também
> que o `Blob` não herda nada do bloco do cofre acima dele, embora ambos se refiram ao Azure — a credencial do
> cofre concede *uso de uma chave*, a credencial do blob concede *leitura de um objeto*, e elas são
> configuradas separadamente de propósito.
### Um cofre, dois objetos, duas credenciais
O `AppSecret` acima é uma **referência do Key Vault** (Key Vault reference) do App Service — o único
ponto desta página em que o Key Vault aparece nos seus dois papéis ao mesmo tempo. Parece circular, mas
não é:

- A **identidade gerenciada** do web app resolve a referência e obtém o segredo — uma permissão
`Key Vault Secrets User`, sobre o *segredo*.
- Esse segredo é, então, o que o conector do PKI apresenta para acessar a **chave** — uma permissão
`Key Vault Crypto User` do *registro de aplicativo*, sobre a *chave*, delimitada por
`-GrantScope Key` acima.
Duas entidades de segurança, dois objetos, duas permissões. O mesmo cofre guarda ambos porque um segundo
cofre não traria isolamento algum — quem consegue ler o segredo consegue usar a chave de qualquer forma —
e só acrescentaria mais um recurso. A observação do passo 4 de que segredos devem ficar em referências do
Key Vault se refere a *este* mecanismo; a chave em si nunca é um app setting.
### A latência é a restrição aqui, não o throttling
[Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#origem--azurekeyvault) alerta que concorrência sustentada provoca
respostas HTTP 429, que aparecem como jobs com falha, e não como travamentos. Vale saber disso, e vale
conhecer a escala: o Key Vault permite 2.000 transações a cada 10 segundos para uma chave RSA-2048 em
HSM — cerca de **200 assinaturas por segundo**, para um produto que faz uma assinatura por job. A
latência de ida e volta vai limitar você muito antes do teto.
O que é específico do cluster é a *inversão* do tema recorrente desta página. Em outros pontos desta
topologia, as cotas se multiplicam com as instâncias, a favor do atacante; aqui a cota é **por cofre** e
fixa, enquanto os consumidores se multiplicam. `Pipeline__MaxConcurrency=4` é por instância, então duas
instâncias significam oito assinaturas em andamento contra um mesmo cofre. O `AzureKeyVault` é tratado
como seguro para concorrência e está isento do aviso de inicialização que se aplica a `Pkcs11` e
`WindowsStore`.
### Renovação e rotação são janelas de mudança
Três coisas aqui são lidas **uma vez, no boot**, e nada volta a consultá-las: os bytes do certificado no
blob, a chave do cofre indicada por `KeyName` e o client secret do registro de aplicativo. Mudar qualquer
uma delas exige, portanto, um restart — e um restart nesta topologia é a parada total do
[passo 8](https://docs.lacunasoftware.com/articles/bulk-signer/azure#8-atualizações-exigem-parada-total), e não um rolling restart nem uma troca de slot. Planeje uma
renovação ICP-Brasil, e a expiração de `-SecretValidityYears 2`, como indisponibilidades agendadas, e
não como manutenção que pode ser feita a qualquer momento.
**Depois do primeiro boot, o client secret fica no perfil armazenado, e não no app setting.** O seed o
copiou, criptografado, para o banco operacional, e a referência do Key Vault em `…__AppSecret` não é
lida de novo. Para rotacioná-lo, portanto: crie o novo segredo no registro de aplicativo, informe-o na
página do perfil (*Editar certificado*, digitando o novo valor — um campo em branco mantém o armazenado)
e reinicie. É no mesmo formulário que se indica um `.cer` renovado ou um novo `KeyName`; ao salvar, o
perfil fica marcado como aguardando reinicialização, e a mudança entra em vigor no restart.
> **Perigo: Renove um artefato sem o outro e o perfil não consegue assinar**
>
> Um certificado novo com o `KeyName` antigo, ou uma chave nova com o `.cer` antigo, reprova na
> verificação de pareamento. O host sobe mesmo assim, mas aquele perfil fica **degradado** e todo job
> roteado para ele falha com `profile.degraded` — em uma implantação com um único perfil, a janela de
> mudança termina sem nada sendo assinado. Execute novamente o script de importação com o novo PFX, para
> que os dois artefatos sejam atualizados juntos, e confira a linha `profile` no
> [passo 6](https://docs.lacunasoftware.com/articles/bulk-signer/azure#6-primeiro-boot-em-uma-instância) antes de considerar a janela encerrada.
### O que este passo pressupõe sobre a rede
Todos os comandos acima acessam o respectivo serviço por um endpoint público com uma credencial, e o
mesmo vale para o SQL, o Azure Files e o blob do certificado nesta página. Restringir qualquer um deles
exige integração com VNet e private endpoints, que é o [passo 9](https://docs.lacunasoftware.com/articles/bulk-signer/azure#9-reforce-a-segurança-da-rede-opcional),
opcional.
## 4. App settings
Os app settings do App Service são **por app, não por instância** — e é nesse fato que toda a
arquitetura se apoia: todas as instâncias são idênticas por construção, então os riscos de divergência
de configuração entre instâncias (uma senha de criptografia diferente entre hosts, um perfil que uma
instância desconhece) não existem aqui. Defina-os como variáveis de ambiente, na forma com duplo
sublinhado; não há um `appsettings.Production.json` para montar.
```bash
az webapp config appsettings set --name bulksigner --resource-group bulksigner-rg --settings \
WEBSITES_PORT=8080 \
Cluster__Enabled=true \
Database__Provider=SqlServer \
ConnectionStrings__Default='Server=tcp:sqlsrv01.database.windows.net,1433;Initial Catalog=BulkSigner;Authentication=Active Directory Managed Identity;Encrypt=True;' \
Storage__Provider=AzureFiles \
Storage__AzureFiles__AccountName=contosofiles \
Storage__AzureFiles__ShareName=bulksigner \
Storage__AzureFiles__Directory=prod \
Storage__AzureFiles__Credential=ManagedIdentity \
Storage__Inputs__0__Name=remessas \
Storage__Inputs__0__Path=entrada/remessas \
Storage__Inputs__0__PollIntervalSeconds=30 \
Hosting__ForwardedHeaders__Enabled=true \
Hosting__ForwardedHeaders__TrustAnyProxy=true \
Logging__AzureTable__Enabled=true \
Telemetry__Enabled=true
```
**Esse bloco é a topologia, não a configuração inteira.** Ele omite de propósito tudo o que é igual
aqui e em qualquer outro alvo — a licença do PKI, a chave de API, os perfis de assinatura, os pools de
aprovadores. A única exceção é o **certificado** de um perfil, que definitivamente não é igual aqui e
nos outros alvos, e foi definido no [passo 3](https://docs.lacunasoftware.com/articles/bulk-signer/azure#3-a-chave-de-assinatura-fica-em-um-cofre), logo acima.
Copie o restante do arquivo `appsettings.Example.Azure.json.sample` do pacote de implantação, que traz
um exemplo completo, preenchido de ponta a ponta, e converta cada chave para a forma com duplo
sublinhado (`Signing:Profiles[0].Certificate.Source` →
`Signing__Profiles__0__Certificate__Source`). Toda configuração `Signing__Profiles__*` e
`Storage__Inputs__*__Profile` é **entrada do seed**, lida somente no primeiro boot; depois que os
perfis estão no banco, eles — incluindo pool, quórum, pasta de entrada e certificado — são editados pelo
dashboard, de forma idêntica para todas as instâncias. Um perfil cujo certificado não pode ser resolvido
não interrompe o boot: ele sobe **degradado**, identificado no banner e no `/api/ready`, e todos os
outros perfis continuam assinando.
Segredos — `Signing__PkiSdkLicense`, `Auth__ApiKey`, `Signing__ProfileSecretsKey`,
`ApproverPortal__LinkSecret`, `CloudHub__ApiKey`, qualquer `AppSecret` — devem ficar em referências do
Key Vault, e não em app settings literais, resolvidas pela identidade gerenciada do web app, que tem a
permissão `Key Vault Secrets User`. O passo 3 usa exatamente esse mecanismo para o próprio client secret
do cofre, e explica ali por que isso não é circular. Todas as chaves que este produto aceita, com tipo,
padrão e forma de variável de ambiente, estão em
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration).
Cinco das configurações acima são específicas do cluster, e vale conhecer o motivo de cada uma:
- **`Cluster__Enabled=true`** é o único interruptor. Nada é inferido do ambiente: uma implantação que por
acaso rode SQL Server e um compartilhamento do Azure Files não muda de comportamento silenciosamente
numa atualização.
- **`Cluster__HeartbeatSeconds` / `Cluster__StaleAfterSeconds`** estão ausentes acima porque os padrões
(15 e 60) são o ponto de partida correto — quatro cadências, de modo que três heartbeats precisam
faltar antes de uma instância ser presumida morta. Um limiar abaixo de **três cadências** é recusado
no boot, com uma mensagem que cita as duas chaves. Aumente `StaleAfterSeconds` se ocorrerem falsas
mortes; veja
[por que uma morte presumida é uma suposição](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability#uma-morte-presumida-é-uma-suposição).
- **`Hosting__ForwardedHeaders__*`** é o que faz o limitador de requisições e o endereço registrado em
uma aprovação enxergarem o cliente real, e não o balanceador de carga. `TrustAnyProxy=true` é a
configuração pretendida *aqui* — o front end do App Service não tem endereço estável para listar — e
somente aqui: em uma implantação com proxy reverso, isso significa que qualquer pessoa que alcance o
Kestrel diretamente pode se apresentar com qualquer endereço de cliente. Um conjunto de confiança vazio
é recusado no boot, em vez de ser interpretado como a opção mais ampla, e definir também o
`ASPNETCORE_FORWARDEDHEADERS_ENABLED` do próprio framework é recusado.
- **`WEBSITES_PORT=8080`** corresponde ao `EXPOSE` da imagem. O App Service detecta automaticamente as
portas 80 e 8080, então isto é uma precaução redundante — e uma configuração custa menos que o
diagnóstico quando uma imagem futura mudar de porta.
- **Não defina `Hosting__RequireHttps`.** Faça a terminação TLS na plataforma com o `httpsOnly` abaixo.
Um redirecionamento no próprio processo responde ao ping do health check com um 307, que o App
Service interpreta como falha — veja [a nota sobre o health check](https://docs.lacunasoftware.com/articles/bulk-signer/azure#o-health-check-lê-o-endpoint-de-readiness) abaixo.
> **Observação: Um cluster de homologação com os certificados de teste da Lacuna**
>
> O App Service não define nome de ambiente, então o app roda como `Production` — ambiente no qual o
> [`Signing:TrustLacunaTestRoot`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingtrustlacunatestroot--certificados-de-teste-para-homologação)
> é recusado no boot. Um app de homologação que queira os certificados de teste Turing / Fermat define
> `Signing__TrustLacunaTestRoot=true` **e** `ASPNETCORE_ENVIRONMENT=Staging` no mesmo comando. Nunca defina
> o primeiro em um app que assine algo real.
## 5. Configurações de plataforma
```bash
az webapp update --name bulksigner --resource-group bulksigner-rg --set clientAffinityEnabled=true httpsOnly=true
```
```bash
az webapp config set --name bulksigner --resource-group bulksigner-rg --always-on true --generic-configurations '{"healthCheckPath": "/api/ready"}'
```
**A afinidade ARR continua ligada.** Ela vem ligada por padrão e é um requisito, não uma preferência: o
dashboard é Blazor Server, e o circuito dele é uma conexão SignalR com estado que precisa continuar
chegando à instância que o mantém. Esse comportamento é documentado, e não contornado com engenharia.
Note o que ela *não* faz — o cookie de sessão em si é compartilhado, porque no modo cluster o key ring
de Data Protection passa para o banco operacional, de modo que um cookie emitido por uma instância é
validado por todas as outras. As sticky sessions servem ao circuito; o key ring, ao cookie. Sem o key
ring, só a afinidade ainda desconectaria as pessoas de forma intermitente.
**O Always On continua ligado.** O pipeline de assinatura é um worker hospedado, não um manipulador de
requisições. Um app que a plataforma descarrega quando ocioso para de pegar arquivos, e nada disso
aparece como erro em lugar algum.
### O health check lê o endpoint de readiness
O `/api/ready` é anônimo, por instância, e retorna `503` quando qualquer um dos seus probes falha —
exatamente a pergunta que o App Service está fazendo. Aponte o Health check para ele e a plataforma deixa
de rotear para uma instância que não consegue atender, e por fim substitui a que continuar assim.
Ele é anônimo *por causa* desse consumidor: o Health check não consegue enviar a chave de API (a
Microsoft documenta que o caminho precisa permitir acesso anônimo quando o app tem autenticação
própria), então o `Readiness:RequireApiKey` fica no padrão `false` aqui. Ligá-lo faria a plataforma
interpretar o `401` de todas as instâncias como "não saudável" ao mesmo tempo — nenhuma é removida da
rotação, a plataforma começa a substituí-las, uma por hora, e a métrica do Health check fica vermelha
enquanto a exigência estiver ligada. O corpo anônimo traz apenas o nome e o veredito de cada
verificação; o diagnóstico — servidor e catálogo, URLs de compartilhamentos, a mensagem de falha de um
SDK — fica no `/api/ready/details`, protegido pela chave de API.
Três consequências a aceitar conscientemente:
- **Deixe `WEBSITE_HEALTHCHECK_MAXPINGFAILURES` no padrão (10).** O endpoint é rigoroso — uma única
pasta de entrada ausente reprova a resposta inteira — então um limiar apertado transforma uma
oscilação em uma remoção.
- **A falha de uma dependência *compartilhada* derruba todas as instâncias juntas.** Um compartilhamento
que para de responder ou um banco que estava inalcançável no boot não é problema de uma só instância,
e a regra da plataforma é que, quando todas as instâncias estão não saudáveis, nenhuma é removida do
balanceador de carga — mas a substituição continua acontecendo, no máximo uma por hora e três por dia
por plano. O app continua alcançável; as instâncias vão sendo recicladas enquanto a falha real está em
outro lugar. Leia [Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting) antes de concluir
que a culpa é da plataforma.
- **O Health check não segue redirecionamentos.** É por isso que `Hosting:RequireHttps` fica desligado:
com ele ligado e o `httpsOnly` da plataforma desligado, o ping recebe um 307 e a instância é marcada
como não saudável por um motivo que nada tem a ver com sua saúde.
## 6. Primeiro boot, em uma instância
Inicie o app e acompanhe o console — é no streaming de logs do App Service que aparecem as mensagens do
boot:
```bash
az webapp log tail --name bulksigner --resource-group bulksigner-rg
```
O que procurar no painel **Service ready**:
| Linha | O que ela deve dizer |
|---|---|
| `operational store` | `SQL Server (sqlsrv01/BulkSigner)` — o provider, o servidor e o catálogo, nunca a connection string. Um aviso `READ_COMMITTED_SNAPSHOT is off` merece atenção: no Azure SQL, essa opção vem ligada por padrão. |
| `storage provider` | `AzureFiles` |
| `work share owner` | `this cluster (one marker, shared between instances)` — o texto que confirma que o modo está de fato ligado. Com o modo desligado, esta linha mostra o nome de uma instância. |
| `azure shares` | alcançáveis. Um compartilhamento inalcançável **não** impede o host de subir, mas reprova o `/api/ready`, e nada é ingerido dele até que volte a responder. |
| `forwarded headers` | o conjunto de confiança, por nome, e não apenas `on`. |
| `logs` | o destino em tabela entre os destinos listados. Se ele estiver ausente, você também terá visto o Critical sobre arquivos de log rotacionados em disco efêmero. |
| `profile` | o certificado que de fato foi carregado — `cades · cert=AzureKeyVault · blob=contosocerts/certificates/signer.cer · verify=on · …`. As duas partes são evidências: `cert=AzureKeyVault` significa que o cofre respondeu e a chave foi encontrada, `blob=…` significa que o `.cer` foi lido **e pareado** com ela. Uma linha com o prefixo `DEGRADED · ` significa que o certificado não foi resolvido, com o motivo ao lado. Esta é a linha a conferir após qualquer renovação de certificado — é a única confirmação de que os dois artefatos foram atualizados juntos. |
Depois, faça um `GET /api/ready/details` com a chave de API e confirme que todas as verificações estão
verdes, abra **Perfis de assinatura** para confirmar que o seed importou o que você pretendia e abra
**Sistema → Instâncias** no dashboard: uma linha, marcada como a instância em que você está navegando,
com um chip **Viva**.
> **Aviso: Vai atualizar uma implantação de instância única existente em vez de criar uma nova?**
>
> Suba **uma vez** com `Cluster:Enabled = false` e deixe a recuperação rodar antes de ligar o modo. Uma
> linha deixada em andamento por um build mais antigo não tem dono, e no modo cluster nada jamais vai
> recuperá-la — cada boot trata apenas da sua própria encarnação anterior, os jobs de uma instância irmã
> ficam com ela, e a assunção depende do heartbeat de um dono, que aqui não existe. O mesmo vale para um
> job enviado ao Lacuna Signer por um build assim. As duas interfaces avisam quando encontram um caso
> desses e indicam esta mesma solução. É uma preocupação única, no momento da atualização: o dono é
> registrado em toda reivindicação, com o modo ligado ou não, e só é *lido* com o modo ligado.
## 7. Escale para duas
```bash
az appservice plan update --name bulksigner-plan --resource-group bulksigner-rg --number-of-workers 2
```
Aguarde um minuto — o App Service consulta o caminho do health check para confirmar que a nova instância
está pronta antes de rotear para ela — e recarregue **Sistema → Instâncias**.
**O resultado esperado:** duas linhas, cada uma com uma identidade derivada distinta (o
`WEBSITE_INSTANCE_ID` da plataforma, que o App Service define em toda instância), cada uma **Viva**,
ambas na mesma versão da aplicação, uma delas marcada como a instância que respondeu à sua requisição.
A legenda mostra a cadência e o limiar de inatividade em vigor. Atualize algumas vezes: a marcação muda
de linha, porque cada recarga pode cair em qualquer uma das instâncias — é o balanceador de carga
fazendo o seu trabalho, e a primeira coisa nesta página que você pode ver, em vez de deduzir.
### Depois, confirme que o cluster de fato coopera
A visão Instâncias prova que ambas as instâncias estão vivas. Ela não prova que elas dividem o
trabalho.

Coloque vários arquivos de uma vez em uma pasta monitorada e confira `/jobs`:
- Cada arquivo vira exatamente **um** job. As duas instâncias monitoram todas as pastas, então disputam
cada arquivo que chega; o enfileiramento perdedor é recusado pelo índice único parcial sobre os
caminhos originais ativos e respondido como `AlreadyActive`, e um conflito de lease em uma entrada é
classificado como **esperado** — não conta para o disjuntor de falhas consecutivas da pasta nem é
registrado como erro. Aqui, contenção é sinal de que o sistema funciona, e não uma falha.
- Os jobs **pertencem às duas instâncias**. O `/api/folders` traz um campo `instance` que indica qual
delas respondeu; a linha do tempo do job em `/jobs/{id}` mostra o restante.
- Pare uma instância (volte a escala para uma) e o trabalho em andamento dela é reconciliado pela
sobrevivente, em vez de ficar órfão: um job que nunca chegou à chamada de assinatura volta para a
fila, um que já passou dela falha por precaução, e um job `AwaitingSigner` é reatribuído. Cada
assunção grava um evento operacional `JobTakenOver` com o nome das duas instâncias. A política
completa, inclusive por que "falhou" é um desfecho honesto, e não "travado", está em
[Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quando-uma-instância-para-de-responder-uma-sobrevivente-assume-seus-jobs).
## 8. Atualizações exigem parada total
Pare o app, implante a nova imagem e inicie-o — com um passo antes desses três, porque a imagem vem do
registry da Lacuna, e não de um build:
```bash
az acr import --name \
--source /bulksigner: \
--image bulksigner: \
--username --password
az webapp stop --name bulksigner --resource-group bulksigner-rg
az webapp config container set --name bulksigner --resource-group bulksigner-rg \
--container-image-name .azurecr.io/bulksigner:
az webapp start --name bulksigner --resource-group bulksigner-rg
```
Não é um rolling restart, e **não é uma troca de deployment slot** — um slot de staging com a connection
string de produção é um segundo conjunto de instâncias entrando no cluster em outra versão da aplicação,
justamente o único cenário que esta arquitetura não suporta.
**O `stop` torna a implantação mais limpa, mas não é obrigatório.** Mudar a imagem de um app em execução
inicia o container novo ao lado do antigo, na mesma instância; ambos derivam a mesma identidade do
`WEBSITE_INSTANCE_ID`, e a plataforma mantém o antigo rodando até o novo estar aquecido. Desde a 2.5.0, o
container novo **desloca** o antigo imediatamente e o antigo **se retira** — não pega nada novo e termina
o que já tem —, então um `container set` in-place funciona, ao custo de um aviso com o nome da encarnação
deslocada; se a plataforma derrubar o container antigo antes, os jobs inacabados dele são assumidos
`Cluster:StaleAfterSeconds` depois. Parando primeiro, nada se sobrepõe: o container antigo encerra sua
linha de heartbeat ao desligar, e o novo não tem nada a deslocar. (Até a 2.4.x, uma troca in-place
custava pelo menos um início recusado.)
Duas coisas a saber sobre uma troca in-place:
- **Se o container novo falhar no aquecimento, volte para a tag anterior.** O App Service para o site
inteiro — inclusive o container que se retirou — e continua tentando reiniciá-lo com a imagem nova; o
container antigo não tem como retomar o trabalho. A recuperação é
`az webapp config container set … --container-image-name .azurecr.io/bulksigner:`.
- **Alguns segundos depois de a plataforma remover o container antigo**, o primeiro comando ao banco em
cada conexão que o novo abriu durante o aquecimento pode falhar uma vez com um
`Execution Timeout Expired` de 35 segundos. É uma conexão cortada junto com o container antigo, e não
um problema no banco: uma falha por conexão desse tipo, e depois o pool se recupera sozinho. Uma
implantação que para o app primeiro não tem esse efeito.
A marca de versão no heartbeat funciona como alarme, e não como uma verificação que bloqueia: uma
instância que, ao subir, encontra heartbeats ativos de outra versão registra um **Critical e continua**.
De propósito, isso não é uma recusa — recusar impediria as instâncias de subir por todo o tempo que um
heartbeat *morto* da versão antiga levasse para expirar, que é exatamente quando um operador precisa
delas no ar.
O argumento completo, e o resto do que esta topologia não oferece, está em
[Alta disponibilidade e seus limites](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability).
## 9. Reforce a segurança da rede (opcional)
Tudo acima resulta em um cluster funcional, acessado por endpoints públicos com credenciais. Esta seção
é o que você acrescenta depois que ele funciona, e ela é realmente opcional — mas um dos itens não é
defesa genérica, e sim uma limitação documentada desta topologia que se transforma em problema
resolvido.
**As cotas param de se multiplicar.**
[Alta disponibilidade](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability#as-cotas-do-limite-de-requisições-são-por-instância-então-o-limite-efetivo-é-n)
lista isso como um custo da escala horizontal: cada instância impõe a sua própria cota por cliente,
então duas instâncias dão a um atacante o dobro da cota na rota de aprovação, e *N* instâncias, *N*
vezes. Um limite de requisições no WAF do Front Door fica **antes** do balanceador de carga, o que o
torna o único ponto desta arquitetura em que uma cota por cliente é aplicada uma única vez, e não por
instância. Nada mais nesta página resolve isso.
O motivo para não fazer: esta seção acrescenta quatro recursos cobrados e a mudança para uma camada
superior, além de um plano e de um cofre que já são Premium. Não há valores aqui — os preços do Azure
ficam desatualizados mais rápido que qualquer documento.
**Para a entrada, há dois modelos, e só um deles pode valer por vez.** Ou o app permanece na internet
atrás de um Front Door — o resto deste passo — ou ele sai completamente da internet e passa a ser
acessado por VPN, que é o [passo 10](https://docs.lacunasoftware.com/articles/bulk-signer/azure#10-nenhum-endpoint-público-opcional) e traz consequências que vão
além da rede. A parte de saída, abaixo, é o mesmo trabalho nos dois casos. Leia os dois antes de
implementar qualquer um — o segundo é a resposta mais forte para a rota de aprovação anônima e a mais
fraca para tudo o que um WAF faz, e qual desses aspectos importa mais é uma pergunta que esta página não
pode responder por você.

### Entrada — Front Door na frente do app
**O Front Door Premium com uma origem via Private Link é a recomendação**, porque o App Service
desabilita automaticamente seu endpoint público de internet quando a origem é alcançada por Private
Link — o que, neste produto, traz um benefício específico, descrito dois parágrafos abaixo.
A alternativa mais barata é o Front Door Standard mais uma restrição de acesso do App Service:
```bash
az webapp config access-restriction add --name bulksigner --resource-group bulksigner-rg --rule-name frontdoor --priority 100 --service-tag AzureFrontDoor.Backend --http-header x-azure-fdid=
```
As duas partes dessa regra são obrigatórias, e é o header que faz a diferença. A Microsoft deixa
explícito que a filtragem por IP sozinha não basta, *porque as instâncias de Front Door de outros
clientes do Azure usam as mesmas faixas de endereço* — a service tag `AzureFrontDoor.Backend` prova que o
tráfego veio por *um* Front Door, e só o `X-Azure-FDID` prova que veio pelo **seu**. Nunca liste as faixas
de endereço manualmente em vez de usar a tag; esses endereços mudam com frequência.
**É isto que finalmente torna o `TrustAnyProxy=true` do passo 4 verdadeiro, e não apenas pretendido.**
Lá, essa configuração é justificada pelo fato de o front end do App Service não ter endereço estável
para listar, com o aviso de que, numa implantação com proxy reverso, ela permite que qualquer pessoa que
alcance o Kestrel diretamente se apresente com qualquer endereço de cliente. Neste produto, esse
endereço não é cosmético: ele é registrado em cada aprovação como um dos controles compensatórios da
rota de aprovação anônima, e é por ele que o limitador de requisições faz a contagem. Um Front Door
**sem** bloqueio de origem ampliaria esse risco e ainda passaria a impressão de proteção. Com o Private
Link, o risco desaparece por completo, porque não sobra endpoint público para acessar; com a regra do
Standard, ele só desaparece na medida em que aquela restrição de acesso se sustentar — uma garantia mais
fraca, que deve ser tratada como tal.
Mantenha `TrustAnyProxy=true` nas duas camadas. **Não** migre para
`Hosting__ForwardedHeaders__KnownNetworks` — ele recebe CIDRs literais, as faixas do Front Door mudam, e
uma lista desatualizada falha descartando silenciosamente o endereço real do cliente, em vez de recusar
alguma coisa. O produto recusa `TrustAnyProxy` junto com uma lista explícita, então é realmente uma coisa
ou outra, e a resposta correta aqui é a confiança ampla com a origem bloqueada.
Três interações de configuração a acertar:
- **Deixe ligada a afinidade de sessão do próprio Front Door.** O dashboard é um circuito Blazor Server
e precisa de fixação de ponta a ponta; a afinidade do Front Door é o que mantém um cliente na origem
cujo cookie ARR ele carrega. As duas camadas cooperam — o Front Door escolhe a origem, o ARR
escolhe a instância.
- **Não aponte o health probe do Front Door para `/api/ready`.** Há exatamente uma origem aqui, então
uma falha do probe não tem para onde fazer failover e simplesmente tira a aplicação inteira do ar. O
próprio Health check do App Service já monitora esse endpoint e *pode* agir com base nele,
redirecionando entre instâncias. Dê ao Front Door um caminho leve ou deixe o probe dele no padrão.
- **O domínio personalizado e o TLS gerenciado passam para o Front Door.** O `httpsOnly=true` do App
Service fica exatamente como o passo 5 o deixou, e o `Hosting:RequireHttps` continua sem ser definido,
pelo motivo que o passo 5 explica.
> **Observação: O que isso não oferece: múltiplas regiões**
>
> Aqui, o Front Door é um WAF, um terminador de TLS e uma camada de proteção contra DDoS, e não um
> balanceador de carga global para esta aplicação. Uma segunda região seria um segundo conjunto de
> instâncias sobre o mesmo compartilhamento de trabalho, e a verificação do marcador do compartilhamento
> recusa isso por design — veja
> [Alta disponibilidade](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability#a-verificação-do-compartilhamento-de-trabalho-cobre-menos-do-que-a-catástrofe-que-motivou-sua-criação).
### Saída — integração com VNet e private endpoints
Integre o web app a uma sub-rede e crie um private endpoint para **cada um dos quatro serviços de plano
de dados**: o Azure SQL, o compartilhamento do Azure Files, o cofre e o blob do certificado. Os quatro, e
não três — com private endpoint na maioria deles, o que sobrar anula a razão de ser da rede virtual, e
ninguém percebe até que uma auditoria perceba.
Duas exclusões, ambas deliberadas:
- **A tabela de logs continua pública.** Ela é o destino que precisa continuar funcionando quando todo
o resto falhou; um destino de log não pode depender justamente de um caminho de rede que pode falhar,
já que um destino incapaz de relatar a própria falha é uma indisponibilidade que se esconde.
- **O Application Insights continua público.** A ingestão privada exige um Azure Monitor Private Link
Scope, um recurso à parte, com consequências próprias de DNS para todos os recursos que o
compartilham — uma decisão maior que esta seção, que deve ser tomada para uma assinatura do Azure
inteira, e não para um único web app.
## 10. Nenhum endpoint público (opcional)
As duas opções de entrada são **mutuamente exclusivas**, e esta é a segunda: se você implementou o
Front Door do [passo 9](https://docs.lacunasoftware.com/articles/bulk-signer/azure#9-reforce-a-segurança-da-rede-opcional), não implemente esta. Em vez de colocar um Front
Door na frente de um app público, crie um **private endpoint** para o app e depois remova o endpoint
público dele, de modo que a única rota até ele parta da sua própria rede: uma VPN site a site ou ponto a
site, ou o peering privado do ExpressRoute. A [Saída](https://docs.lacunasoftware.com/articles/bulk-signer/azure#saída--integração-com-vnet-e-private-endpoints) é
comum aos dois modelos e se aplica aqui sem mudança — o que este passo substitui é a parte de entrada
que o diagrama do passo 9 descreve. O argumento da cota, na abertura do passo 9, vale só para o Front
Door; este modelo trata a questão de outra maneira, e não a ignora — *O que este modelo não resolve*,
abaixo, explica como.

Escolha este modelo quando o requisito for que **ninguém fora da sua rede alcance este serviço de forma
alguma**, e o Front Door quando o serviço precisar ser acessível pela internet e a única questão for com
que nível de segurança.
**A rota de aprovação anônima deixa de ser acessível pela internet, e esse é o ganho mais importante
desta página.** O `/approve/{jobId}` e o `POST /api/approvals/{id}` não exigem credencial, por design —
uma decisão compensada, e não escondida, com um método de identificação registrado, o endereço e o user
agent do aprovador, uma cota própria e recusas deliberadamente genéricas ([Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals)).
Cada um desses controles foi projetado para uma página que qualquer pessoa poderia carregar. Remova o
endpoint público e quem consegue alcançar a rota passa a ser apenas quem já tem acesso à sua rede, uma
compensação mais forte do que qualquer coisa que o próprio produto possa oferecer. Se essa rota sem
autenticação é o motivo de você estar lendo esta seção, a resposta é este modelo, e não o Front Door.
**Pelo outro lado, esse também é o custo: um aprovador que não está na rede não consegue aprovar.** O
link do portal é um HMAC derivado que funciona em qualquer dispositivo, e este modelo transforma o
*acesso à rede* na restrição determinante. Levante quem são os aprovadores antes de decidir — um diretor
aprovando uma folha de pagamento pelo celular, um auditor externo, qualquer pessoa em um cliente —,
porque, para cada um deles, a solução passa a ser um cliente de VPN naquele dispositivo, e é muito
melhor saber disso agora do que depois que a primeira folha ficar retida num fim de semana.
**O que este modelo não resolve.** Não há WAF aqui, então a cota ×N continua ×N; o que muda é quem pode
consumi-la, não a aritmética. O `/api/metrics` continua caindo em uma instância arbitrária, e as
atualizações continuam exigindo parada total. Se você precisa de um WAF **e** de nenhuma exposição
pública, a resposta é um Application Gateway interno em vez do Front Door — que sempre tem um front end
público próprio —, e isso é um terceiro modelo de implantação, não documentado aqui.
**Mantenha `Hosting__ForwardedHeaders__*` exatamente como o [passo 4](https://docs.lacunasoftware.com/articles/bulk-signer/azure#4-app-settings) o deixou, e o
endereço registrado fica mais preciso.** O Private Link encaminha o endereço real do cliente para o app,
então o que aparece em um registro de aprovação é o endereço do próprio aprovador, e não o de um salto
de proxy. O `TrustAnyProxy=true` também deixa de ter a ressalva que o passo 4 faz a ele, pelo mesmo
motivo da opção de origem via Private Link: com o acesso público desabilitado, não sobra, fora da rede,
nenhum caminho até o Kestrel que possa ser explorado.
### Provisione o private endpoint
O private endpoint precisa de uma sub-rede própria — **não** a que a integração com VNet usa. Isso é uma
restrição da plataforma, e não uma preferência, e é o motivo mais comum para esta etapa ter de ser
refeita:
```bash
az network private-endpoint create --resource-group bulksigner-rg --name bulksigner-pe --vnet-name bulksigner-vnet --subnet inbound --group-id sites --connection-name bulksigner-sites --private-connection-resource-id $(az webapp show --name bulksigner --resource-group bulksigner-rg --query id -o tsv)
```
```bash
az network private-dns zone create --resource-group bulksigner-rg --name privatelink.azurewebsites.net
```
```bash
az network private-dns link vnet create --resource-group bulksigner-rg --zone-name privatelink.azurewebsites.net --name bulksigner-vnet-link --virtual-network bulksigner-vnet --registration-enabled false
```
```bash
az network private-endpoint dns-zone-group create --resource-group bulksigner-rg --endpoint-name bulksigner-pe --name default --private-dns-zone privatelink.azurewebsites.net --zone-name azurewebsites
```
```bash
az resource update --ids $(az webapp show --name bulksigner --resource-group bulksigner-rg --query id -o tsv) --set properties.publicNetworkAccess=Disabled
```
**Esse último comando é o que realmente resolve, e criar o private endpoint não o torna desnecessário.**
Por padrão, um private endpoint e o acesso público **coexistem** em um App Service: crie o endpoint,
conecte-se pela VPN, veja funcionar — e o app continua na internet exatamente como antes. Note também
que as regras de restrição de acesso do app *não* são avaliadas para o tráfego que chega pelo private
endpoint, então a regra `x-azure-fdid` da opção do Front Door não tem efeito aqui. Este modelo é
garantido pela inexistência de um endpoint público, nunca por uma regra.
> **Aviso: É no DNS que isso falha, e a falha aparece como um 403, não como um timeout**
>
> O nome público do app continua sendo resolvido depois que o endpoint existe — para um endereço público
> que agora recusa o acesso. Um cliente sem resolução privada configurada recebe, portanto, um **403 da
> plataforma**, que parece um problema de permissão, mas não é. São necessários dois registros A em
> `privatelink.azurewebsites.net`, um para o app e outro para o nome `scm` dele; o grupo de zona DNS acima
> cria os dois, e é por isso que vale usá-lo em vez de criar os registros manualmente. **Clientes de VPN
> resolvem nomes pelo seu próprio DNS**, então a última etapa é um encaminhador condicional dos seus
> resolvedores para o Azure — um endpoint de entrada do
> [Azure DNS Private Resolver](https://learn.microsoft.com/azure/dns/dns-private-resolver-overview) é a
> forma gerenciada de fazer isso. Continue usando o nome de host padrão `*.azurewebsites.net`: o
> certificado da plataforma é emitido para ele, e um domínio personalizado aqui exige trazer o seu próprio
> certificado.
**Duas coisas que você fez em passos anteriores passam a depender da rede interna.** O
[passo 6](https://docs.lacunasoftware.com/articles/bulk-signer/azure#6-primeiro-boot-em-uma-instância) lê as mensagens do boot com `az webapp log tail`, que acessa
o site SCM do app — agora privado; portanto, execute o comando em uma estação conectada à VPN, ou leia o
boot no destino de log em tabela. E, se você também criar um private endpoint para o container registry,
o pull da imagem precisa ser roteado explicitamente pela rede, definindo `properties.vnetImagePullEnabled`
do mesmo jeito que o passo 2 define `acrUseManagedIdentityCreds`; um registry que continua público não
precisa de nada. O que **não** muda é tudo o que a plataforma faz internamente — o
[Health check](https://docs.lacunasoftware.com/articles/bulk-signer/azure#o-health-check-lê-o-endpoint-de-readiness) continua consultando `/api/ready` e a
afinidade ARR continua fixando o circuito Blazor, ambos internos ao App Service e indiferentes a tudo
isso.
> **Observação: "Sem exposição à internet" aqui se refere à entrada, e duas coisas ainda saem**
>
> O app mantém o caminho de saída para a internet, e as duas exclusões deliberadas em
> [Saída](https://docs.lacunasoftware.com/articles/bulk-signer/azure#saída--integração-com-vnet-e-private-endpoints), acima, dependem dele: o destino de log em
> tabela do Azure e o Application Insights. Se o seu requisito também abrange a saída, toda a discussão
> gira em torno desses dois — o Application Insights precisa de um Azure Monitor Private Link Scope, e a
> tabela de logs precisa de um private endpoint na conta de armazenamento dela, o que **contradiz o motivo
> pelo qual aquela exclusão existe**: um destino de log é o único destino que precisa continuar funcionando
> quando o que falhou foi a rede. Leia esse argumento antes de fechar esse caminho, e note que simplesmente
> desligar o destino não é a saída fácil — no modo cluster, isso registra um Critical na inicialização,
> porque os arquivos de log rotacionados de um container somem junto com ele
> ([Antes de começar](https://docs.lacunasoftware.com/articles/bulk-signer/azure#antes-de-começar), item 5).
---
## Quando algo é recusado
Todos os modos de falha do cluster, com a mensagem exata e a correção de cada um, estão em
[Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#modo-cluster). Os mais prováveis em uma primeira
implantação:
| Sintoma | Causa |
|---|---|
| `Cluster mode refused to start`, citando chaves | Uma das recusas de boot em [Antes de começar](https://docs.lacunasoftware.com/articles/bulk-signer/azure#antes-de-começar). A mensagem lista todas as chaves com problema de uma só vez, em vez de uma por tentativa. |
| **O container nunca inicia** | Ou o pull da imagem falhou (`acrUseManagedIdentityCreds` nunca definido — passo 2), ou houve uma recusa de boot. O fluxo de log distingue os dois casos — uma falha de pull o deixa vazio, porque ainda não há aplicação, enquanto uma recusa cita a chave antes de encerrar. A importação do primeiro boot é recusada, por exemplo, quando um perfil tem um segredo e o `Signing__ProfileSecretsKey` não está definido ([passo 3](https://docs.lacunasoftware.com/articles/bulk-signer/azure#as-configurações)). Leia o fluxo antes de supor que o problema é o registry. |
| O app sobe mas o perfil está `DEGRADED` e todo job falha com `profile.degraded` | O perfil não conseguiu resolver seu certificado: um cofre inalcançável, um client secret expirado ou um `.cer` que não pareia com o `KeyName` ([passo 3](https://docs.lacunasoftware.com/articles/bulk-signer/azure#3-a-chave-de-assinatura-fica-em-um-cofre)). O motivo está na linha `profile` do banner e no `/api/ready/details`. Corrija na página do perfil (*Editar certificado*) ou no cofre, e então reinicie. |
| O boot avisa `displaced the previous incarnation … which was still live` | Uma **reimplantação in-place** ([passo 8](https://docs.lacunasoftware.com/articles/bulk-signer/azure#8-atualizações-exigem-parada-total)): o container novo assumiu a identidade e o antigo se retirou — o esperado em toda troca in-place. Se este host **não** está no meio de uma reimplantação, dois hosts estão usando a mesma identidade e vão se revezar: leia os dois logs e renomeie um deles, ou aponte-o para um banco de dados próprio. |
| Boot recusado dizendo que a identidade `could not be registered in 3 attempts` | Todas as gravações da linha de heartbeat perderam a disputa para outra encarnação: dois hosts com o mesmo nome subindo no mesmo instante contra o mesmo banco de dados, ou uma falha no banco. Encontre o outro host na visão Instâncias de uma instância que esteja rodando. |
| Boot recusado citando dois bancos operacionais | O marcador do compartilhamento de trabalho indica que ele pertence a outro banco operacional. Dois clusters sobre o mesmo compartilhamento de trabalho destroem os dados um do outro sem que banco de dados algum perceba, e é isso que essa verificação existe para detectar. |
| Operadores mandados de volta ao login de forma intermitente | As instâncias não estão compartilhando o mesmo key ring — geralmente um host com `Cluster:Enabled` falso, ou instâncias apontadas para bancos diferentes. |
---
Relacionados: [Alta disponibilidade e seus limites](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability) ·
[Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation) · [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates) ·
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#cluster--implantação-com-múltiplas-instâncias) ·
[Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quais-instâncias-estão-vivas-somente-no-modo-cluster) ·
[Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#modo-cluster)
# [Alta disponibilidade e seus limites](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability)
O modo cluster executa mais de uma instância ativa sobre um mesmo banco de dados operacional e um mesmo
compartilhamento de trabalho. O que ele oferece são as três coisas que se esperavam da funcionalidade:
um job nunca é processado por duas instâncias ao mesmo tempo, a morte de uma instância não deixa
trabalho travado para sempre, e o pipeline continua assinando enquanto um host está fora do ar.
Esta página é a outra metade dessa história — **o que ele não oferece**, dito de antemão, em vez de
descoberto durante uma janela de mudança. Tudo aqui se aplica somente quando `Cluster:Enabled` é
verdadeiro. Com o modo desligado — caso de toda implantação que não o ligou deliberadamente —, nada
disso está em vigor, e o produto continua sendo o de instância única que sempre foi.
O passo a passo de implantação está em [Azure App Service (modo cluster)](https://docs.lacunasoftware.com/articles/bulk-signer/azure); a visão do operador
no dia a dia, em [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quais-instâncias-estão-vivas-somente-no-modo-cluster); o
catálogo de falhas, em [Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#modo-cluster).
---
## Uma única topologia suportada
Um **Azure Web App, container Linux, a imagem existente, com escala horizontal em um App Service
Plan.** Só isso.
Os mecanismos não sabem disso. Toda a coordenação passa pelo banco operacional e pelo compartilhamento
de trabalho, e nada é específico do App Service além da derivação de identidade e desta documentação —
de modo que duas VMs on-premises apontando para um SQL Server rodariam o mesmo código. Essa variação é
**não documentada, não testada e não suportada**, e três mecanismos que só existiriam para esse formato
deliberadamente não foram construídos: impressões digitais de configuração, nomes de instância
atribuídos pelo operador e indireção de nome de certificado por host.
Uma característica do App Service resolve boa parte do problema e é o motivo de a lista ter um único
item: **os app settings são por app, não por instância**, então todas as instâncias são idênticas por
construção. Os riscos de divergência de configuração entre instâncias — uma senha de criptografia
diferente entre hosts, um perfil de assinatura que uma instância desconhece — não podem ocorrer aqui.
Em uma topologia em que podem ocorrer, eles não são tratados.
## Atualizações exigem parada total
Pare o app, implante, inicie. Não há reinício gradual (rolling restart), não há caminho sem
indisponibilidade e **não há deployment slots**.
Um slot de staging com a connection string de produção não é um ambiente de staging — é um segundo
conjunto de instâncias entrando no cluster com outra versão da aplicação e compartilhando a fila de
jobs, a tabela de heartbeat e o compartilhamento de trabalho com a versão que você ainda está rodando. O
swap não cria a condição; o primeiro boot do slot já a cria.
A versão registrada no heartbeat é o **alarme, e não uma verificação que bloqueia**: uma instância que
sobe e encontra heartbeats ativos de outra versão registra um Critical e segue em frente. Isso é
deliberado. Uma recusa rígida impediria as instâncias de subir por todo o tempo que um heartbeat *morto*
da versão antiga levasse para ficar sem sinal — que é exatamente o momento em que um operador precisa
que elas subam, porque é o momento seguinte a uma implantação que falhou.
Portanto, versões mistas são detectadas e informadas, mas nunca impedidas. Trate o Critical como o
alarme que ele é.
**Trocar a imagem in-place é tolerado, mas não é o procedimento recomendado.** Desde a 2.5.0, trocar a
imagem de um app em execução (`az webapp config container set`) não resulta mais em uma inicialização
recusada. O App Service inicia o novo container ao lado do antigo, sob o mesmo id de instância, e mantém
o antigo atendendo até que o novo esteja aquecido; o novo container **desloca** o antigo, que se
retira — termina o que detém e não reivindica nada novo — e informa isso no próprio log, com um evento
`InstanceStoodDown` e na própria página Sistema. Veja
[Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quando-um-boot-encontra-a-própria-identidade-ainda-viva). Uma instância que é
desligada de forma limpa aposenta a própria linha de heartbeat (desde a 2.4.3); então, depois de
*parar, trocar, iniciar*, não há nada a deslocar, e é por isso que esse continua sendo o procedimento
de implantação mais limpo.
**Uma implantação que falha derruba o site, e voltar para a tag anterior é a forma de recuperação.** Se
o novo container falha no aquecimento depois de deslocar o antigo, o App Service não continua roteando
para o container antigo: ele para o **site inteiro**, inclusive o container que se retirou, e continua
reiniciando-o com a imagem nova. Nada que o produto faça consegue impedir isso. Aponte o app de volta
para a tag de imagem anterior com `az webapp config container set`; ele fica pronto de novo em cerca de
dois minutos.
## A afinidade de sessão é obrigatória
O dashboard é Blazor Server, e um circuito é uma conexão SignalR com estado que precisa continuar
chegando à instância que o detém. O App Service vem com a afinidade ARR ligada por padrão, e ela precisa
continuar ligada. Isso é documentado como requisito, em vez de ser contornado no código.
O que a afinidade **não** faz é manter as pessoas autenticadas. Os dois cookies de sessão são payloads
de Data Protection, e no modo cluster o key ring passa para o banco operacional justamente para que um
cookie criado por uma instância seja validado por todas as outras. A afinidade serve ao circuito; o ring
compartilhado serve ao cookie. Desligar o ring compartilhado provocaria encerramentos de sessão
intermitentes que nenhuma configuração de afinidade resolve.
## As cotas do limite de requisições são por instância, então o limite efetivo é ×N
Toda política de limite de requisições (rate limiting) do produto é um limitador por processo. Duas
instâncias significam o dobro de permissões; N instâncias, N vezes mais.
Isso é documentado em vez de corrigido. Um limitador distribuído seria a primeira dependência de
runtime em infraestrutura compartilhada deste produto on-premises, para um controle que é grosseiro por
design, e a conta que mais importa — o argumento do controle compensatório para a rota de aprovação
anônima — foi refeita com o fator ×N e continua válida para o N pequeno com que esta topologia roda. Se
você escalar para além de um punhado de instâncias, refaça essa conta você mesmo em vez de presumir que
ela continua valendo.
Dimensionar uma cota para um cluster significa dividi-la pelo número de instâncias que você de fato
roda — e lembrar que esse número muda quando você escala.
O `Pipeline:MaxConcurrency` se multiplica da mesma forma, e nesse caso isso é uma vantagem, e não uma
limitação: ele é por instância, então uma frota de duas instâncias com concorrência quatro assina até
oito arquivos ao mesmo tempo. Dimensione a origem do certificado para esse número, e não para o valor
configurado.
## A coleta de métricas alcança uma instância arbitrária
O `/api/metrics` é por processo, e o front-end do App Service não consegue direcionar a requisição para
uma instância específica. Uma coleta do Prometheus, portanto, cai na instância que o balanceador de
carga tiver escolhido, e a série coletada pula entre instâncias de uma coleta para outra. **A
continuidade da coleta se perde**, e nenhuma configuração a recupera.
O caminho de observabilidade recomendado para o cluster é a distro do Application Insights — opcional
e nativamente ciente das instâncias. Veja [Telemetria](https://docs.lacunasoftware.com/articles/bulk-signer/telemetry).
Se você mantiver o Prometheus mesmo assim, dois gauges têm significados definidos por decisão, e não
por dedução, e lê-los de forma errada leva a números subestimados:
- `bulksigner_jobs_awaiting_signer` conta as linhas em que **esta instância** faz polling. A `sum()` na
frota é o total do cluster, sem contagem dupla, já que um job tem exatamente um dono. Ler a série de
uma única instância como se fosse o total é o erro previsível.
- O mesmo raciocínio vale para todos os contadores por instância da página. Os números de uma instância
são apenas daquela instância.
O `GET /api/folders` tem um campo `instance` pelo mesmo motivo, para que um cliente automatizado consiga
ao menos distinguir "a pasta mudou" de "a resposta veio de outra instância".
## Os logs são efêmeros a menos que você os torne duráveis
O disco de um container Linux desaparece na reciclagem, e os arquivos de log rotacionados vão junto. O
diretório `logs/` de `Storage:Root` fica dentro do container.
O modo cluster **avisa e sobe** quando `Logging:AzureTable:Enabled` é falso: um Critical na
inicialização aponta a perda. Ele não recusa o boot, seguindo a própria escala de severidade do
produto — um compartilhamento de trabalho inacessível e um banco inacessível também geram aviso e
deixam subir, e recusar o boot por causa de um fluxo de diagnóstico inverteria essa escala. A regra de
nunca ter um único destino de log permanece intacta, então o destino de arquivo continua ligado de
qualquer forma, em disco efêmero, onde o streaming de logs do App Service o lê ao vivo.
> **Aviso: Ligar o destino de tabela tem um custo próprio, e é uma decisão a tomar antes de habilitá-lo**
>
> **Nada faz a limpeza dessa tabela**, e nenhum mecanismo do Azure consegue fazer. Veja
> [Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention#logs-em-uma-tabela--nada-os-poda) e agende o script de limpeza.
## Uma morte presumida é uma suposição
A detecção de instâncias vivas se baseia em heartbeats no banco operacional. Uma instância que está
**viva, mas não consegue gravá-los** — isolada do banco por uma partição de rede, ou travada além de
`Cluster:StaleAfterSeconds` — pode ter o trabalho assumido enquanto ainda o está executando. O caso
desfavorável é declarado, e não escondido.
O que limita o dano não mudou em relação à operação com uma única instância, e os três mecanismos são
anteriores ao modo cluster:
- O hash dos bytes da cópia preparada é **recalculado imediatamente antes de qualquer assinatura ser gerada**.
- Uma promoção para um destino já ocupado é **recusada** — uma conclusão duplicada vira um job
`Completed` e um `Failed`, nunca dois artefatos entregues.
- Uma entrada é **comparada com a impressão digital registrada no stage** antes de ser apagada.
Aumente `Cluster:StaleAfterSeconds` quando uma implantação enfrentar isso com frequência. O mínimo é
três vezes a cadência de heartbeat, e valores abaixo disso são recusados no boot, porque um limite tão
curto presume a morte depois de um ou dois heartbeats perdidos, e um heartbeat se perde por motivos que
não são a morte da instância.
O caso inverso também está declarado: uma instância que fica sem sinal para as irmãs **continua
assinando**. Ela não é parada, porque a regra permanente deste produto é que um job em andamento roda
até a conclusão natural. Ela nunca assume os próprios jobs, diga a tabela o que disser.
**A mesma janela define a frequência da pergunta sobre uma instância deslocada.** Depois da sobreposição
de uma reimplantação, passada a janela, a varredura do novo container pergunta ao banco pelos jobs que a
encarnação deslocada deixou para trás. Depois que essa pergunta volta vazia, ela é repetida uma vez a
cada `Cluster:StaleAfterSeconds`, e não a cada ciclo de polling (desde a 2.8.0) — no App Service, toda
troca de versão tem sobreposição, então o registro de deslocamento nunca é limpo. O custo é que, se um
processo deslocado nunca se retirou e reivindica um job tardiamente, esse job é assumido em até uma
janela mais um ciclo de polling, e não em um ciclo de polling — a mesma suposição descrita acima, e não
uma nova.
## Linhas que ninguém possui não são reconciliadas por ninguém
Uma linha de job **sem dono** — deixada por uma build anterior à coluna de dono, ou por uma execução com
o modo desligado — é uma linha que o modo cluster jamais varrerá. A recuperação no boot só considera a
identidade da própria instância, a de uma irmã só considera a dela, e a assunção se baseia no heartbeat
de um dono, que nesse caso não existe.
Isso é informado em vez de adotado. Adotar significaria um filtro que casa com nulo, e esse filtro
casaria em *todas* as instâncias ao mesmo tempo — o defeito que a funcionalidade elimina, voltando pelo
próprio código que o elimina.
> **Observação: A solução existe, e é indicada em todos os lugares que encontram uma dessas linhas**
>
> **Suba uma vez com `Cluster:Enabled = false`**, o que varre todas as linhas em andamento, seja quem for
> o dono, e então religue o modo. Faça isso uma vez na atualização, antes do primeiro boot em cluster, e
> a questão deixa de existir — o dono é registrado em toda reivindicação, com o modo ligado ou não, e só é
> *lido* com o modo ligado.
Dois pontos específicos merecem ficar por escrito, porque são piores do que parecem:
- **Um job despachado ao Lacuna Signer sem dono não recebe polling de ninguém e não expira mais.** O
`Signer:TimeoutHours` é aplicado enquanto uma linha recebe polling; então, uma linha que ninguém
consulta é uma linha sem limite algum. Restringir o polling às linhas com dono eliminou o último
caminho que levava um job assim a um estado terminal. O worker de polling avisa isso uma vez por
processo e informa a quantidade.
- **Uma linha detida por uma instância *nomeada* que não tem linha de heartbeat nenhuma** fica órfã do
mesmo jeito e exige a mesma solução. A ausência de heartbeat não é prova de morte — um dono sem linha
é informado uma vez e deixado como está, em vez de ser interpretado como permissão para fazer falhar o
trabalho vivo de alguém.
## Não existe drenagem por instância
Você não pode pedir à instância B que termine o que tem e pare de pegar trabalho novo. O
`POST /api/pipeline/pause` retém o worker de **todas** as instâncias — a flag de pausa fica na única
linha que todo worker lê a cada iteração de polling, então o controle existente passa a valer para o
cluster inteiro, e é isso que um operador que pausa "o pipeline" pretende.
A resposta para "aplicar um patch na instância B" é pará-la e deixar a assunção fazer o seu trabalho. A
drenagem por instância deliberadamente não foi construída.
Uma consequência de a pausa valer para o cluster inteiro merece ser conhecida: **a assunção fica sujeita
à pausa.** Um operador que pausa um cluster para investigar um banco que ficou lento é exatamente a
pessoa que não pode ver todas as instâncias declarando mortas todas as irmãs. Já a varredura de
expiração de aprovações não é afetada pela pausa, porque um prazo de espera é contado em tempo de
relógio, e pausar não o estende.
## A latência entre instâncias é o intervalo de polling
O sinal de despertar é local ao processo. Um enfileiramento na instância A não acorda o worker da
instância B; B pega o job no próximo ciclo de polling. O `Pipeline:PollIntervalSeconds` é, portanto, o
limite de latência entre as instâncias do cluster — aceito e documentado, e não contornado no código.
**A edição de um perfil de assinatura segue o mesmo limite, por projeto.** Os perfis de assinatura ficam
no banco operacional e são editados pelo dashboard; uma gravação de perfil incrementa um contador no
banco, dentro da própria transação, e todas as instâncias leem esse contador no polling que já fazem —
então, uma mudança feita na instância A chega à instância B em um intervalo de polling, sem nenhum canal
de mensagens entre elas para configurar, proteger ou depurar. O que se propaga é o *comportamento*:
formato, verificação, criptografia, verificação CNAB240 e toda a regra de aprovação. Uma mudança de
**certificado** não se propaga, em nenhuma instância: cada host abre o seu certificado uma vez na
inicialização e mantém o handle da chave, então trocar um certificado exige um reinício — nesta
topologia, o reinício com parada total descrito acima.
Duas consequências a esperar:
- **Entre a edição de um certificado e o reinício, duas instâncias podem legitimamente informar origens
diferentes para o mesmo perfil** — cada uma indicando a chave com que assinaria, que é também a origem
que ela registra nos jobs que assina. Uma coleta ou uma requisição ao dashboard cai em uma instância
arbitrária, então leia a resposta como "o que assinaria aqui", e não como "o que a linha diz".
- **O indicador de reinício pendente que uma edição de certificado aciona é por instância, e é fiel.**
Uma instância que já reiniciou não mostra nada, e uma que não reiniciou mostra o indicador — ambas
corretas sobre si mesmas —, o que torna legível uma reciclagem parcial. No reinício com parada total,
todas as instâncias perdem o indicador ao mesmo tempo.
**Duas origens de certificado não podem ser escolhidas nesta topologia.** Um token PKCS#11 e o
repositório de certificados do Windows do próprio host ficam em uma única máquina, e estas instâncias
são criadas e destruídas por operações de escala — então criar um perfil com qualquer uma dessas
origens, ou mudar um perfil para elas, é recusado no formulário enquanto `Cluster:Enabled` estiver
ligado. Uma configuração que declare uma delas continua sendo recusada no boot. Um perfil **salvo antes
de a chave ser ligada** é o único caso que nenhuma das duas verificações pega: nada revalida um perfil
armazenado, então ele gera um aviso na inicialização, com o nome do perfil e a origem. Espere que isso
pareça assimétrico na frota: a instância que tem o token assina e avisa, enquanto todas as instâncias
sem ele informam o perfil como degradado e fazem falhar os jobs roteados para ele.
A mesma localidade é o que faz a reingestão continuar funcionando sem custo extra: no esquema em que
todas as instâncias monitoram todas as pastas, a instância que termina um job sempre monitora a pasta de
onde ele veio, então o sinal local ao processo ainda chega a um observador responsável por aquele
caminho.
## A verificação do compartilhamento de trabalho cobre menos do que a catástrofe que motivou sua criação
O marcador vincula um compartilhamento de trabalho a um banco operacional, e uma instância que sobe com
um banco que não corresponde ao marcador se recusa a iniciar, indicando os dois. Isso pega o cenário para
o qual ele existe: uma segunda implantação encontrando um compartilhamento que um cluster já
estabelecido marcou.
O que ele não pega é qualquer momento em que o marcador esteja **ilegível**, porque ele só recusa diante
de evidência, e nunca diante da falta dela. Existem dois desses momentos — um compartilhamento que ainda
não tem marcador, e o instante de uma gravação de identificação no Azure Files, em que o arquivo fica
brevemente preenchido com zeros. Os dois são reduzidos, mas não eliminados, por uma verificação extra
quando um lease está detido. Uma verificação que roda uma única vez no boot também não consegue
enxergar um cluster rival que apareça depois.
E essa verificação **não** é o que impede duas instâncias de assinarem o mesmo arquivo. Isso é feito
pelo lease por arquivo e pela reivindicação no banco. O marcador existe para a única catástrofe que
banco de dados nenhum consegue enxergar: dois bancos, um compartilhamento.
## O backup de banco de dados não está disponível aqui
`Backup:Enabled = true` com `Database:Provider = SqlServer` é recusado no boot, com uma mensagem que
cita as duas chaves — e o modo cluster exige `SqlServer`. Portanto, essa combinação é impossível por
construção, o que é uma consequência conveniente, e não uma lacuna: a trava de backup por processo não
precisa de um substituto distribuído.
Nesta topologia, fazer backup do banco operacional cabe à rotina do seu SGBD. O point-in-time restore
do próprio Azure SQL é a resposta, e não uma funcionalidade deste produto. Veja
[Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention#disciplina-de-backup).
## O key ring de sessão fica em texto claro no banco
No modo cluster, o ring de Data Protection são linhas em `SessionProtectionKeys`, em texto claro,
protegidas pelo controle de acesso do próprio banco de dados — coerente com um modelo de segurança em
que a connection string **é** a credencial e em que o acesso de leitura a `keys/` já está documentado
como "uma sessão como qualquer pessoa".
Duas coisas decorrem disso, e a segunda é a que passa despercebida com facilidade:
- **O encriptador DPAPI do Windows é descartado com a chave ligada.** O DPAPI com escopo de máquina é
justamente a propriedade que torna uma cópia de `keys/` inútil em outro host — e justamente a
propriedade que torna um ring ilegível para uma irmã, de modo que mantê-lo seria manter o defeito. No
Windows, isso deixa o ring mais fraco em repouso. Não custa nada na topologia suportada, cujo
container Linux também não tem criptografia em repouso para o ring em disco, e é o único ponto em que
ligar o modo troca um controle por outro, em vez de acrescentar um.
- **Um banco inacessível faz a requisição falhar, sem alternativa.** Um host que não conseguisse chegar
ao banco e criasse sessões silenciosamente a partir de um ring por instância emitiria cookies que as
irmãs rejeitam — o encerramento de sessão intermitente que o ring compartilhado elimina, voltando pelo
próprio código que o elimina. Essa falha aparece como a exceção do próprio provider no caminho da
requisição, e não como uma recusa diagnosticada que cita o ring. A condição é informada onde é
diagnosticada: na verificação de boot e na linha `database` por instância do `/api/ready`.
Uma observação sobre o primeiro boot, registrada para que não seja interpretada como falha: instâncias
que iniciam juntas encontram todas um ring vazio, então várias podem criar um elemento antes que
qualquer uma tenha lido o da outra, e a tabela pode ter mais elementos do que houve rotações de chave.
Não há nada de errado nisso.
## A contenção tem um custo, e ele é pequeno
Duas instâncias que reivindicam jobs da mesma fila entram em conflito com frequência, e a reivindicação
em lote passa a reivindicar um job por vez, com a disputa perdida registrada no log. No modo cluster,
essa linha de log é rebaixada ao nível de desfecho esperado, com um id de evento próprio — "uma irmã
chegou primeiro" e "outro componente desta instância chegou primeiro" são fatos diferentes para quem lê.
Relacionados, e deliberadamente **não** tratados como falha: um conflito de lease em uma entrada e um
desfecho de enfileiramento `AlreadyActive`. Todas as instâncias monitoram todas as pastas, então perder
uma disputa é rotina — nenhum dos dois conta para o disjuntor de falhas consecutivas de uma pasta, e um
desfecho `AlreadyActive` zera o contador exatamente como um enfileiramento bem-sucedido. Veja
[Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#contenção-entre-instâncias-não-é-uma-falha).
## O que o modo cluster não muda
Esta lista existe porque cada item é uma regra que alguém poderia razoavelmente esperar que uma
funcionalidade de cluster tivesse flexibilizado, e nenhuma delas foi flexibilizada:
- **Sem repetição automática de assinaturas.** Uma assinatura nunca é tentada de novo sem que uma pessoa
decida isso. A política de assunção segue essa fronteira deliberadamente: um job que nunca chegou à
chamada de assinatura é reenfileirado porque *nada foi tentado*; um job que passou dela falha.
`Failed` é um desfecho terminal honesto, não "travado", e a nova tentativa manual do operador continua
sendo a forma de repetir. Um job que assina um documento do Lacuna Signer e passou desse ponto é antes
consultado no Signer, e concluído ou cancelado quando a resposta do Signer resolve a questão; ele só
falha com `signer-folder.interrupted` quando ela não resolve (veja
[Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#quando-a-execução-que-o-assinava-termina)).
- **Toda instância varre as pastas vinculadas do Lacuna Signer.** Com um perfil `SignerFolder`, cada
instância lista as pastas a cada `Signer:FolderSweepIntervalSeconds`; um documento encontrado por duas
instâncias é assumido uma vez só, porque o banco operacional registra cada par (documento, ação do
fluxo) já assumido e recusa o segundo. As linhas de prontidão `signer-notifications` e
`signer-folder-sweep` valem por instância.
- **Jobs em andamento são intocáveis.** O `POST /api/jobs/{id}/cancel` só é válido para jobs que nenhum
worker está executando — `Queued`, `AwaitingSigner` e `AwaitingApproval` —, em todas as instâncias.
- **As pastas monitoradas continuam na configuração; os perfis de assinatura, não mais.** Mover os
perfis para o banco de dados chegou a ser considerado como pré-requisito para o cluster e foi
descartado quando a topologia se definiu, porque o App Service garante consistência entre instâncias
por construção. Depois, a mudança veio por mérito próprio: os perfis são linhas no banco operacional,
editadas pelo dashboard, o que transfere a edição dos pools de aprovadores do acesso a arquivos do host
para a autorização do dashboard. O que isso acrescenta aqui é o limite de propagação descrito acima e
nada mais — nenhuma topologia nova e nenhuma coordenação entre instâncias. As pastas monitoradas
continuam sendo configuração, e todas as instâncias continuam monitorando todas as pastas.
- **A etapa de aprovação não mudou.** A regra continua sendo fixada no job no momento em que ele fica
retido, uma rejeição continua sendo um veto, e o hash dos bytes da cópia preparada continua sendo recalculado
antes de qualquer assinatura ser gerada.
- **O Limpar Jobs remove todos os jobs, inclusive os de uma irmã — por decisão, e não por omissão.** Da
2.0.0 à 2.8.x, ele só removia jobs em estado terminal, para todo mundo, porque apagar a linha de um
job que uma irmã estava executando era a ação de operador mais arriscada do inventário. Desde a 2.9.0,
ele apaga todos os jobs, qualquer que seja o status: um operador que limpa o sistema pela zona de
perigo limpou, de propósito, um sistema com um job dentro, e o diálogo avisa isso. O worker do job
abandonado o marca como falho, não encontra a linha e registra as duas coisas no log — e é o lease por
arquivo, e não esta ação, que continua impedindo duas instâncias de assinarem o mesmo arquivo. Veja
[Limpar Jobs](https://docs.lacunasoftware.com/articles/bulk-signer/operations#limpar-jobs).
## O que não é uma limitação, apesar de parecer
- **A pausa vale para o cluster inteiro.** Uma chamada retém todas as instâncias, que é o que um
operador quer dizer ao pausar "o pipeline".
- **As estatísticas valem para o cluster inteiro.** Elas passaram para o banco operacional e são
calculadas na leitura, então o painel descreve a frota, e não a instância que por acaso respondeu. O
que era excluído antes continua excluído: as esperas pelo assinante e pela aprovação, e o uso de
`QueuedAt`, e não de `CreatedAt`, como referência da espera na fila. Veja
[Estatísticas de jobs](https://docs.lacunasoftware.com/articles/bulk-signer/statistics).
- **Links de aprovador, segundos fatores e sessões funcionam entre instâncias.** A janela de verificação
fica no banco operacional, indexada por um identificador que vai dentro do cookie, algo que é
inteiramente anterior ao cluster — de modo que uma janela aberta por uma instância é respeitada por
outra sem nada a acrescentar.
---
Relacionados: [Azure App Service (modo cluster)](https://docs.lacunasoftware.com/articles/bulk-signer/azure) · [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation) ·
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#cluster--implantação-com-múltiplas-instâncias) ·
[Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quais-instâncias-estão-vivas-somente-no-modo-cluster) ·
[Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#modo-cluster)
# [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration)
Todas as chaves do `appsettings.json` do Lacuna Bulk Signer — tipo, padrão, override por variável de
ambiente e se são obrigatórias.
## Origens de configuração, em ordem de precedência
Origens posteriores sobrescrevem as anteriores:
1. `appsettings.json` (padrões embutidos)
2. `appsettings.{Environment}.json` (por exemplo, `appsettings.Production.json`)
3. `appsettings.json` + `appsettings.{Environment}.json` encontrados em `BULK_SIGNER_CONFIG_DIR`
4. Variáveis de ambiente (`Section__Sub__Key`)
É a etapa do `BULK_SIGNER_CONFIG_DIR` que permite manter o binário em um local de instalação somente
leitura (`/opt/bulksigner`, `%ProgramFiles%\Lacuna\BulkSigner`) e a configuração de produção, editada
pelo operador, em outro lugar (`/etc/bulksigner`, `%ProgramData%\Lacuna\BulkSigner\config`). Os
scripts de instalação definem essa variável; se você mudar os caminhos de instalação, atualize a
variável também.
O mapeamento para variáveis de ambiente segue a regra do ASP.NET Core: uma chave JSON como
`Signing:Certificate:Pfx:Password` corresponde a `Signing__Certificate__Pfx__Password` (o sublinhado
duplo é o separador).
> **Aviso: Mudou na 2.3.1 — o appsettings.json distribuído é neutro para produção**
>
> O `appsettings.json` que acompanha os binários e a imagem **não declara perfis de assinatura, nem bloco
> `Signer`, nem segredo de nenhum tipo** — nem mesmo um `Auth:ApiKey` de exemplo. Isso importa porque ele
> é lido com qualquer nome de ambiente, e a configuração pode sobrescrever uma chave, mas nunca removê-la:
> antes da 2.3.1, os perfis de exemplo desse arquivo se mesclavam por baixo dos perfis declarados em
> variáveis de ambiente. A única consequência na atualização: uma implantação que nunca definiu o próprio
> `Auth:ApiKey` estava rodando com o antigo valor de exemplo e agora **se recusa a iniciar, indicando a
> chave**. Defina `Auth__ApiKey` como descrevem todos os roteiros de instalação.
## Marcadores usados nas tabelas
| Marcador | Significado |
|----------|-------------|
| **REQUIRED** | O serviço se recusa a iniciar (ou a assinatura se recusa a rodar) sem um valor não vazio. |
| **SECRET** | Sensível — prefira o override por variável de ambiente a um valor gravado em um arquivo versionado. |
## `Logging` / `Logging:File`
As configurações padrão do `Microsoft.Extensions.Logging` (`Logging:LogLevel:*`) funcionam
normalmente; o bloco `Logging:File` configura o destino de arquivo.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Logging:LogLevel:Default` | string | `Information` | `Logging__LogLevel__Default` | Nível de log padrão. |
| `Logging:LogLevel:Microsoft.AspNetCore` | string | `Warning` | `Logging__LogLevel__Microsoft.AspNetCore` | Reduz o volume de mensagens do framework. O mesmo nível mínimo `Warning` é aplicado no código a `Microsoft.AspNetCore` e à categoria de comandos de banco de dados do Entity Framework Core, de modo que nem o pipeline de requisições nem cada instrução SQL executada chegam ao log em `Information`. Comandos que falham continuam sendo registrados. |
| `Logging:File:Path` | string | `data/logs/bulksigner-.log` | `Logging__File__Path` | **REQUIRED.** Template de caminho do destino de arquivo. O `-` final antes de `.log`, somado à rotação diária, produz `bulksigner-yyyyMMdd.log`. |
| `Logging:File:RollingInterval` | string | `Day` | `Logging__File__RollingInterval` | Um de `Day`, `Hour`, `Minute`, `Infinite`. |
| `Logging:File:FileSizeLimitBytes` | long | `50000000` | `Logging__File__FileSizeLimitBytes` | Limite por arquivo; acima dele, o destino passa a escrever em `…_001.log`. Faixa válida: 64 KB a 10 GB. |
| `Logging:File:RetainedFileCountLimit` | int | `14` | `Logging__File__RetainedFileCountLimit` | Arquivos mais antigos são apagados conforme a rotação avança. Faixa válida: 1–365. |
| `Logging:File:MinimumLevel` | string | `Information` | `Logging__File__MinimumLevel` | Um de `Verbose`, `Debug`, `Information`, `Warning`, `Error`, `Fatal`. |
| `Logging:File:WriteToConsole` | bool | `true` | `Logging__File__WriteToConsole` | Quando verdadeiro, também escreve na saída padrão. O mesmo formatador com mascaramento roda nos dois destinos. |
## `Logging:AzureTable` — um segundo destino de log
Uma implantação em um sistema de arquivos efêmero — um container que é substituído em vez de
reiniciado — perde `data/logs/` toda vez. Este bloco envia os eventos de log também para uma tabela do
Azure Storage, de modo que o fluxo de diagnóstico sobreviva ao host. É a última de três partes do estado
que podem ir para o Azure: os arquivos já podem ficar em um compartilhamento do Azure Files
([`Storage:Provider`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#storageprovider--storageazurefiles--o-compartilhamento-de-trabalho)), e o
registro operacional, no Azure SQL ([`Database`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#database-e-connectionstrings)).
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Logging:AzureTable:Enabled` | bool | `false` | `Logging__AzureTable__Enabled` | Desligado, a menos que seja definido. `true` com um bloco incompleto causa uma **recusa de boot** que indica a chave ausente, e não um destino silenciosamente inativo. |
| `Logging:AzureTable:TableName` | string | `bulksignerlogs` | `Logging__AzureTable__TableName` | Precisa existir previamente — o serviço não a cria. Somente caracteres alfanuméricos, de 3 a 63, sem começar com dígito; um nome inválido é recusado no boot, e não na primeira escrita. |
| `Logging:AzureTable:MinimumLevel` | string | *(herda `Logging:File:MinimumLevel`)* | `Logging__AzureTable__MinimumLevel` | Restringe o que vai para a tabela sem restringir o arquivo. **Defini-lo *abaixo* do mínimo global não tem efeito** — esse filtro é aplicado antes de qualquer destino. |
| `Logging:AzureTable:ServiceUri` | string | — | `Logging__AzureTable__ServiceUri` | **REQUIRED quando habilitado.** O endpoint da tabela, por exemplo `https://contosologs.table.core.windows.net`. Uma URL com **query string é recusada** — é assim que chega uma shared access signature (SAS), e a SAS deliberadamente não está entre as credenciais de armazenamento deste produto. A recusa também garante que este valor não seja secreto, o que permite exibi-lo com segurança no banner de inicialização. |
| `Logging:AzureTable:Credential` | string | — | `Logging__AzureTable__Credential` | **REQUIRED quando habilitado.** `ManagedIdentity`, `ServicePrincipal` ou `AccountKey`. **Não tem valor padrão** — veja abaixo. |
| `Logging:AzureTable:AccountName` | string | — | `Logging__AzureTable__AccountName` | Obrigatório para `AccountKey` **e somente nesse modo**: a credencial de chave compartilhada precisa do nome da conta, que não pode ser extraído do `ServiceUri` sem adivinhar se a URL é um endpoint de produção ou de emulador. |
| `Logging:AzureTable:AccountKey` | string | — | `Logging__AzureTable__AccountKey` | **SECRET.** Somente no modo `AccountKey`. |
| `Logging:AzureTable:TenantId` | string | — | `Logging__AzureTable__TenantId` | Somente no modo `ServicePrincipal`. |
| `Logging:AzureTable:AppId` | string | — | `Logging__AzureTable__AppId` | Somente no modo `ServicePrincipal`. |
| `Logging:AzureTable:AppSecret` | string | — | `Logging__AzureTable__AppSecret` | **SECRET.** Somente no modo `ServicePrincipal`. Override por env recomendado. |
| `Logging:AzureTable:QueueLimit` | int | `10000` | `Logging__AzureTable__QueueLimit` | Eventos mantidos em memória enquanto a tabela está inacessível. Acima desse limite, os eventos são **descartados e contabilizados** — e informados em `/api/ready` e em `bulksigner_log_sink_dropped_total`. O limite existe porque, com um buffer ilimitado, uma indisponibilidade longa derrubaria por falta de memória um serviço de assinatura só porque o *log* falhou. |
| `Logging:AzureTable:BatchSizeLimit` | int | `100` | `Logging__AzureTable__BatchSizeLimit` | Eventos por escrita. Limitado a 100, o teto documentado de uma transação de grupo de entidades; acima disso, um lote deixa de ser uma única transação, em vez de apenas ficar mais lento. |
| `Logging:AzureTable:BatchPeriodSeconds` | int | `5` | `Logging__AzureTable__BatchPeriodSeconds` | Quanto tempo um lote parcial espera antes de ser escrito. |
**As chaves de credencial são as mesmas usadas por `Storage:AzureFiles` e pelo blob de material de
assinatura de um perfil**, de modo que um operador que já configurou uma credencial de armazenamento
neste produto já sabe configurar todas. A identidade precisa da role **Storage Table Data Contributor**
na tabela ou na conta de armazenamento.
**Um bloco que não indica a credencial é recusado no boot.** Recorrer a uma identidade de
desenvolvimento faria uma identidade gerenciada ausente ou não atribuída funcionar em um laptop e falhar
só em produção.
**Prefira `ManagedIdentity` a `AccountKey`.** A Microsoft recomenda proibir a autorização por Shared
Key, e muitas organizações definem `allowSharedKeyAccess = false` na conta — nesse caso, o `AccountKey`
nem pode ser configurado. Uma chave de conta também concede acesso total ao plano de dados da conta
*inteira*, então uma chave usada para uma tabela de logs também dá acesso a qualquer compartilhamento
do Azure Files que exista na mesma conta.
### Duas regras que são impostas, não recomendadas
**A tabela nunca pode ser o único destino.** O `Logging:File:Enabled` é a chave para desligar o arquivo
(em um sistema de arquivos raiz somente leitura), mas uma configuração em que **os dois destinos locais
estejam desligados** é recusada no boot: se a tabela parar de aceitar escritas, a própria falha de
registro não poderia ser registrada. Mantenha o destino de arquivo ou o `WriteToConsole` — em um
container, ele corresponde à saída padrão, que o `docker logs` e o streaming de logs do App Service já
capturam.
> **Perigo: Nada limpa a tabela**
>
> Sem TTL, sem regra de ciclo de vida, sem exclusão em lote — a tabela cresce até você apagar registros
> dela. Leia [Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention#logs-em-uma-tabela--nada-os-poda) *antes* de habilitar este
> recurso, e agende o script de limpeza. Se duas implantações compartilham uma conta de armazenamento,
> use uma tabela para cada uma.
## `Database` e `ConnectionStrings`
O **banco de dados operacional** — jobs e seu histórico, eventos operacionais, a flag de pausa do
pipeline, as regras de aprovação congeladas e as aprovações registradas — fica em SQLite por padrão,
mas pode ficar em um SQL Server 2022+ próprio ou em um Azure SQL Database.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Database:Provider` | enum | `Sqlite` | `Database__Provider` | `Sqlite` ou `SqlServer`. Não diferencia maiúsculas de minúsculas; se ausente, vale `Sqlite`, então uma implantação existente não precisa configurar nada. Um valor não reconhecido é recusado no boot, com a indicação da chave, do valor informado e dos nomes válidos. |
| `ConnectionStrings:Default` | string | `Data Source=data/db/bulksigner.db` | `ConnectionStrings__Default` | **REQUIRED com `SqlServer`** — e **SECRET** nesse caso, porque contém a credencial inteira (login SQL, Entra ID, identidade gerenciada, autenticação integrada do Windows; não há chave de credencial separada). Com `Sqlite`, pode ser omitida — o padrão acima é de fato usado. Use um caminho SQLite dentro de `Storage:Root` para que uma única montagem cubra a árvore de dados e o banco. |
Deliberadamente, **não há um discriminador `Database:Credential`**: o SQL Server expressa a
autenticação na connection string há trinta anos, e acrescentar um segundo mecanismo ao lado de um que
já funciona só criaria uma forma de os dois divergirem.
O provider do banco operacional é independente de `Storage:Provider` — arquivos em um compartilhamento
do Azure Files com o banco em SQLite, ou o contrário, são combinações comuns. **Nenhuma das combinações,
por si só, torna suportada a execução de mais de uma instância**: para isso existe o
[`Cluster:Enabled`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#cluster--implantação-com-múltiplas-instâncias), que *exige* `SqlServer` e
`AzureFiles`, mas não é ativado por eles. Sem essa chave, veja
[Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quando-outra-instância-parece-ser-dona-do-compartilhamento-de-trabalho).
Azure SQL Managed Instance e SQL Server em uma VM se enquadram em *configure como `SqlServer`, não
testado* — a mesma implementação os atende e não há nenhuma diferença conhecida, mas nenhum dos dois é
testado.
**Três recusas de boot se referem a `ConnectionStrings:Default`,** e quais se aplicam depende do
provider:
- Com `Sqlite`, uma connection string que aponta para um local do Azure Files — acessar um arquivo de
banco de dados por SMB é a forma documentada de corrompê-lo.
- Com `SqlServer`, o inverso: um data source que aponta para um *arquivo* em vez de um servidor, o que
acontece quando uma implantação troca o provider e esquece o caminho SQLite na configuração.
- Com `SqlServer`, uma string **ausente**. Nenhum servidor é presumido, ao passo que, com `Sqlite`,
existe um caminho de arquivo padrão real.
Nenhuma recusa exibe a connection string, porque ela pode conter uma senha; apenas o data source é
citado.
### Antes de apontá-lo para o SQL Server
1. **O banco de dados precisa existir previamente.** O Bulk Signer cria as *tabelas*, não o banco de
dados. O probe de boot abre uma conexão com o banco indicado na connection string; por isso, um
banco ausente é interpretado como banco operacional inacessível, e a migração não é executada.
2. **Um login com as permissões abaixo,** mapeado para um usuário nesse banco de dados.
3. **Criptografia que o cliente aceite.** O `Encrypt` tem padrão `True` no cliente SQL, então um
servidor *on-premises* cujo certificado TLS não é confiável para o host faz o login falhar com um
erro *certificate chain … not trusted*. Instale um certificado confiável no servidor (a solução
correta) ou, conscientemente e somente onde um ataque man-in-the-middle não for uma preocupação,
acrescente `TrustServerCertificate=True`. O Azure SQL não precisa de nenhum dos dois.
**Menor privilégio.** O serviço lê e escreve suas próprias tabelas e aplica migrações no boot. Isso é
`db_datareader` + `db_datawriter` + `db_ddladmin` — **não** `db_owner`:
```sql
-- Uma vez, por um DBA, no banco de dados que o Bulk Signer vai usar.
CREATE USER [bulksigner] FOR LOGIN [bulksigner]; -- um login SQL
-- No Azure SQL com uma managed identity ou service principal, em vez disso:
-- CREATE USER [] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [bulksigner];
ALTER ROLE db_datawriter ADD MEMBER [bulksigner];
ALTER ROLE db_ddladmin ADD MEMBER [bulksigner]; -- para o boot que aplica uma migração
```
Esse bloco pressupõe que o banco de dados e o login já existem e que você está conectado **a esse banco
de dados**. A forma de criá-los varia conforme o engine:
```sql
-- SQL Server: a partir do master, depois troque.
CREATE DATABASE [BulkSigner];
GO
ALTER DATABASE [BulkSigner] SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
GO
CREATE LOGIN [bulksigner] WITH PASSWORD = '';
-- ou, para autenticação integrada do Windows: CREATE LOGIN [DOMINIO\HOSTNAME$] FROM WINDOWS;
GO
USE [BulkSigner];
GO
-- …então o bloco CREATE USER + ALTER ROLE acima.
```
```sql
-- Azure SQL: DUAS conexões, porque USE não troca de banco de dados lá e
-- CREATE DATABASE precisa rodar a partir do master, sozinho.
-- Conexão 1, para o master:
CREATE DATABASE [BulkSigner];
GO
-- Conexão 2, para o próprio BulkSigner: o bloco CREATE USER + ALTER ROLE acima.
```
O `READ_COMMITTED_SNAPSHOT` **já vem ligado** no Azure SQL. Sem ele, as leituras do dashboard obtêm
locks compartilhados e ficam bloqueadas pelas escritas do pipeline — o que aparece como "o dashboard
trava enquanto um lote é assinado", e não como uma configuração de banco de dados. O Bulk Signer
informa essa configuração e **nunca executa o comando que a altera**: isso exige acesso exclusivo a um
banco de dados que pertence a você.
É o `db_ddladmin` que cria as tabelas e os índices, então ele é necessário no **primeiro** boot e em
qualquer boot após uma atualização que traga uma migração. Manter as três roles é o padrão mais simples
e mais seguro; a migração roda a cada boot e não faz nada quando não há o que aplicar.
### As duas formas de credencial
Nos dois casos, a credencial vai na connection string. **Prefira a forma sem senha sempre que o host
puder se autenticar com a própria identidade** — assim não há segredo para rotacionar, vazar em um log
ou ser encontrado em um backup.
```text
# Sem senha — Azure SQL, de um host com uma managed identity atribuída pelo sistema
Server=tcp:sqlsrv01.database.windows.net,1433;Initial Catalog=BulkSigner;Authentication=Active Directory Managed Identity;Encrypt=True;
# Sem senha — on premises, de uma conta de serviço do Windows (autenticação integrada)
Server=sqlsrv01;Initial Catalog=BulkSigner;Integrated Security=True;Encrypt=True;
# Com um segredo — um login SQL
Server=sqlsrv01;Initial Catalog=BulkSigner;User ID=bulksigner;Password=;Encrypt=True;
# Com um segredo — um service principal do Entra
Server=tcp:sqlsrv01.database.windows.net,1433;Initial Catalog=BulkSigner;Authentication=Active Directory Service Principal;User ID=;Password=;Encrypt=True;
```
Para usar uma identidade **atribuída pelo usuário**, acrescente o client id dela como
`User Id=` — diferentemente de `Storage:AzureFiles`, cujo modo `ManagedIdentity` aceita
somente a identidade atribuída pelo sistema, porque aqui é o próprio cliente SQL que obtém o token.
Quando instalado como serviço do Windows, o serviço roda com a conta virtual `NT SERVICE\LacunaBulkSigner`,
que acessa a rede como a **conta de computador** — então o login a criar no SQL Server é
`DOMINIO\HOSTNAME$`, e não o nome da própria conta virtual.
> **Aviso: A variável de ambiente substitui o valor inteiro — não se mescla com o JSON**
>
> `ConnectionStrings:Default` é uma única chave de configuração, então não há como manter o servidor no
> `appsettings.Production.json` e fornecer apenas a senha pelo ambiente. A string completa vem do JSON
> (o que é aceitável quando ela não tem senha) ou do ambiente. Um valor no JSON mantido junto com a
> variável de ambiente é silenciosamente ignorado.
### Trocar de provider começa com um banco vazio
Não há importador nem verificação no boot para um arquivo SQLite que ficou para trás. Uma implantação
que define `Database:Provider = SqlServer` encontra um schema vazio: sem jobs, sem histórico, sem
eventos operacionais — **e sem snapshots de aprovação nem aprovações registradas**, justamente as duas
coisas que o produto, fora isso, retém para sempre, porque são a evidência de quem autorizou um arquivo
de pagamento.
> **Perigo**
>
> **Arquive o antigo `db/bulksigner.db` deliberadamente, antes da troca**, e guarde-o por todo o tempo em
> que a sua política de retenção exigir a evidência que ele contém. Copie-o com o serviço parado e
> mantenha um cliente SQLite à mão. A troca inversa tem a mesma característica. Veja
> [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation#migrando-do-sqlite--arquive-o-arquivo-antigo-primeiro).
### O que o boot informa sobre o banco operacional
Toda implantação recebe uma linha `operational store` no banner de resumo de prontidão, com o provider
e, com `SqlServer`, também o servidor e o banco de dados — nunca a connection string. Uma implantação
`SqlServer` recebe mais duas linhas:
- **`store status`**, resultado de um probe no boot. Um banco operacional inacessível é **informado e
não impede o host de iniciar** (um banco de dados fora do ar durante uma janela de manutenção não pode
transformar uma reinicialização em indisponibilidade); a migração não é executada, o `/api/ready`
fica vermelho até o banco responder, e o próximo boot que o encontrar aplica o schema.
- **`store isolation`**, além de um aviso no console de operação, quando o `READ_COMMITTED_SNAPSHOT`
está desligado. Quando está ligado, nada é informado.
### Comportamento específico do engine que você não configura
- **Com `Sqlite`,** toda conexão recebe `journal_mode=WAL`, `synchronous=NORMAL` e
`busy_timeout=30000`. O WAL impede que as escritas de status por job do pipeline sejam serializadas
no fsync de escritor único do SQLite (o teto de vazão com valores mais altos de
`Pipeline:MaxConcurrency`).
- **Com `SqlServer`, a repetição em falhas transitórias está ligada e não tem ajuste** — a tentativa
inicial mais até seis novas tentativas para os números de erro que o cliente SQL classifica como
transitórios, com cada intervalo crescendo exponencialmente, limitado a 30 segundos. Ela fica ligada
porque, na prática, rodar com o Azure SQL a exige. Deliberadamente, não há chave de configuração: um
limite de repetições que o operador consegue ajustar é um limite que acaba ajustado para zero durante
um incidente.
## `Signing`
A validação falha imediatamente na inicialização se alguma chave obrigatória estiver ausente ou for
inválida.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Signing:PkiSdkLicense` | string | `""` | `Signing__PkiSdkLicense` | **REQUIRED, SECRET.** String de licença do Lacuna PKI SDK (base64). Prefira a forma por variável de ambiente. |
| `Signing:ProfileSecretsKey` | string | `""` | `Signing__ProfileSecretsKey` | **SECRET.** A chave com a qual é criptografado todo segredo mantido por um perfil de assinatura *armazenado*. Deixe-a sem definir, a menos que os perfis da implantação tenham algum segredo — veja [abaixo](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofilesecretskey--a-chave-que-criptografa-os-segredos-dos-perfis-armazenados). |
| `Signing:TrustLacunaTestRoot` | bool | `false` | `Signing__TrustLacunaTestRoot` | Confia também na **raiz da PKI de teste da Lacuna** — a emissora dos certificados de teste Turing / Fermat — em um ambiente de homologação. **Recusada no boot quando `ASPNETCORE_ENVIRONMENT` é `Production`.** Não é segredo. Veja [abaixo](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingtrustlacunatestroot--certificados-de-teste-para-homologação). |
| `Signing:Certificate:Source` | enum | `Pfx` | `Signing__Certificate__Source` | **REQUIRED.** Um de `Pfx`, `Pkcs11`, `WindowsStore`, `AzureKeyVault`. Somente a subárvore correspondente abaixo é consultada. |
### `Signing:ProfileSecretsKey` — a chave que criptografa os segredos dos perfis armazenados
Os perfis de assinatura ficam no banco operacional (veja
[`Signing:Profiles[]`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofiles--perfis-de-assinatura-por-pasta)), então esta chave é
**obrigatória para toda implantação cujos perfis tenham algum segredo** — uma senha de PKCS#12, um
segredo de aplicativo do Azure Key Vault, uma credencial de blob de material de assinatura ou um arquivo
PKCS#12 enviado pelo dashboard. Cada um deles é criptografado com ela, e o banco nunca guarda nenhum
deles em texto claro.
**Defina-a antes do primeiro boot que importa perfis** e antes que alguém crie pelo dashboard um perfil
com segredo. A importação é recusada em vez de gravar um segredo que não consegue proteger, com a
indicação desta chave, da variável de ambiente dela e dos perfis que têm segredo; os formulários de
criação e edição do dashboard recusam da mesma forma. Uma implantação cujos perfis não têm nenhum
segredo — um PFX sem senha, um token PKCS#11 cujo PIN vem de uma variável de ambiente, um certificado do
repositório do Windows, um perfil em que os aprovadores assinam — não precisa de chave, e ela nunca é
solicitada.
Deliberadamente, ela **não** fica no banco de dados: uma chave guardada ao lado do texto cifrado não
protege contra nada que importe. É também por isso que o key ring de sessão não é reaproveitado para
ela — com `Cluster:Enabled`, esse ring é, ele próprio, um conjunto de linhas no mesmo banco (veja
[O key ring de sessão](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#o-key-ring-de-sessão-não-tem-chave-própria-e-não-precisa-de-nenhuma)).
Três casos, tratados de formas diferentes de propósito:
- **Dados protegidos de perfil no banco e nenhuma chave para lê-los** interrompem o boot. A solução é
uma variável de ambiente, e não uma linha em um banco de dados que só seria acessível com o serviço
rodando.
- **Uma importação ou um salvamento que criaria dados protegidos sem chave** é recusado no mesmo
momento, de modo que o banco nunca acaba guardando um valor que ninguém consegue abrir.
- **Uma chave definida, mas *errada*** — rotacionada, restaurada de outro lugar, digitada errado —
**não** causa recusa. O serviço inicia, os perfis que ele não consegue ler sobem **degradados**
(identificados no banner de inicialização e no `/api/ready`), e todo perfil sem segredo continua
assinando. Definir uma variável de ambiente resolve uma chave ausente, mas não resolve uma chave
rotacionada; por isso, a recusa seria permanente.
> **Perigo: Perder esta chave significa digitar novamente o segredo de certificado de cada perfil afetado**
>
> Não há custódia de chaves nem caminho de recuperação, e rotacioná-la tem o mesmo efeito que perdê-la.
> Faça backup dela onde você faz backup da senha de criptografia, do `ApproverPortal:LinkSecret` e do
> `ApproverSecondFactor:SeedSecret`. A recuperação, se for necessária, consiste em digitar novamente a
> senha ou a credencial de cada perfil indicado, na página dele no dashboard, e reiniciar.
O **PIN do PKCS#11 não é um dos valores que ela protege**, e nunca passa a ser: ele continua sendo lido
da variável de ambiente indicada por `Pkcs11:PinEnvVar`, e um PIN gravado em qualquer lugar de onde
possa ser lido de volta é recusado no boot.
### `Signing:TrustLacunaTestRoot` — certificados de teste para homologação
O serviço valida toda assinatura **somente com as raízes da ICP-Brasil** — a chave do perfil quando
assina, o verificador depois, e o certificado de um aprovador antes de o token pedir o PIN —, sem usar
nada do repositório do sistema operacional. A **PKI de teste** pública da Lacuna (os certificados de
teste Turing / Fermat) não está abaixo dessas raízes, então, por padrão, um job assinado com um desses
certificados falha, e um aprovador que apresente um deles é recusado com `approval.certificate-invalid`.
`Signing:TrustLacunaTestRoot = true` amplia o conjunto de confiança para a ICP-Brasil, o conjunto de
confiança do Windows do PKI SDK (o repositório da máquina, no Windows) e a raiz de teste da Lacuna, para
um ambiente de homologação que queira rodar o produto distribuído com os certificados de teste em vez de
comprar um e-CPF real para cada aprovador. Três pontos importantes:
- **Ela é recusada com o nome de ambiente `Production`.** O boot falha indicando a chave, o ambiente e
a solução: um host de homologação usa `ASPNETCORE_ENVIRONMENT=Staging` (ou qualquer nome diferente de
`Production`). O Azure App Service usa `Production` por padrão quando nada define o nome, então, nele,
as duas configurações andam juntas.
- **Ela é um único conjunto de confiança para o host inteiro.** A chave de todo perfil, toda verificação
e o certificado de todo aprovador são validados com as mesmas raízes; não há variante por perfil. A
linha `trust set` do banner de inicialização indica a raiz de teste da Lacuna quando ela está ligada,
e o console e o log durável exibem um aviso a cada boot.
- **Ela não é segredo.** Um certificado raiz é material público, e a chave é um booleano.
Deixe-a sem definir em toda implantação que assine qualquer coisa real.
### `Signing:Certificate:Pfx` — quando `Source = Pfx`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Signing:Certificate:Pfx:Path` | string | `""` | `Signing__Certificate__Pfx__Path` | **REQUIRED a menos que `Blob` esteja definido** — exatamente um dos dois. Caminho absoluto para o arquivo `.pfx`/`.p12`. |
| `Signing:Certificate:Pfx:Password` | string | `""` | `Signing__Certificate__Pfx__Password` | **SECRET.** String vazia é permitida para arquivos de teste sem senha. Prefira a forma por variável de ambiente. |
### `Signing:Certificate:Pkcs11` — quando `Source = Pkcs11`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Signing:Certificate:Pkcs11:ModulePath` | string | `""` | `Signing__Certificate__Pkcs11__ModulePath` | **REQUIRED.** Caminho absoluto para o driver PKCS#11 do fabricante (`.so`/`.dll`/`.dylib`). |
| `Signing:Certificate:Pkcs11:Thumbprint` | string | `""` | `Signing__Certificate__Pkcs11__Thumbprint` | **REQUIRED.** Thumbprint SHA-1 (hexadecimal, sem espaços) do certificado de assinatura no token. Obrigatório mesmo quando o token contém uma única identidade. |
| `Signing:Certificate:Pkcs11:PinEnvVar` | string | `BULK_SIGNER_PKCS11_PIN` | `Signing__Certificate__Pkcs11__PinEnvVar` | Nome da variável de ambiente que fornece o PIN. O validador impede a inicialização se uma chave `Pin` literal aparecer em `Pkcs11`. |
### `Signing:Certificate:WindowsStore` — quando `Source = WindowsStore`
Somente Windows. Na inicialização, o validador recusa esta origem em hosts que não são Windows.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Signing:Certificate:WindowsStore:StoreLocation` | string | `CurrentUser` | `Signing__Certificate__WindowsStore__StoreLocation` | `CurrentUser` ou `LocalMachine`. Use `LocalMachine` quando o certificado foi importado para toda a máquina; a conta de serviço não enxerga o repositório `CurrentUser` do operador. |
| `Signing:Certificate:WindowsStore:StoreName` | string | `My` | `Signing__Certificate__WindowsStore__StoreName` | Nome lógico do repositório. `My` é o repositório pessoal. |
| `Signing:Certificate:WindowsStore:Thumbprint` | string | `""` | `Signing__Certificate__WindowsStore__Thumbprint` | **REQUIRED.** Thumbprint SHA-1 (hexadecimal, sem espaços). |
### `Signing:Certificate:AzureKeyVault` — quando `Source = AzureKeyVault`
A chave privada permanece no cofre, e cada assinatura é uma chamada remota de assinatura; o certificado
público correspondente é fornecido separadamente como um `.cer`. `Endpoint`, `AppId`, `AppSecret` e
`KeyName` são sempre obrigatórios, além de **exatamente um** entre `CerPath` (um arquivo neste host) e
[`Blob`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#blob--lendo-o-arquivo-do-azure-blob-storage) (um objeto no Azure Blob Storage). A
inicialização falha indicando cada um que estiver ausente.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Signing:Certificate:AzureKeyVault:Endpoint` | string | `""` | `Signing__Certificate__AzureKeyVault__Endpoint` | **REQUIRED.** URL do cofre. Precisa ser uma URL `https://` absoluta — um nome DNS puro é recusado na inicialização. |
| `Signing:Certificate:AzureKeyVault:AppId` | string | `""` | `Signing__Certificate__AzureKeyVault__AppId` | **REQUIRED.** ID de aplicativo (client) do registro de aplicativo do Microsoft Entra ID. |
| `Signing:Certificate:AzureKeyVault:AppSecret` | string | `""` | `Signing__Certificate__AzureKeyVault__AppSecret` | **REQUIRED, SECRET.** Client secret do Entra ID. Diferentemente do PIN do PKCS#11, este *é* permitido em arquivo de configuração, mas a forma por variável de ambiente é recomendada. |
| `Signing:Certificate:AzureKeyVault:KeyName` | string | `""` | `Signing__Certificate__AzureKeyVault__KeyName` | **REQUIRED.** Nome do objeto **key** no cofre que executa a assinatura. Um objeto *certificate* do cofre não é aceito. |
| `Signing:Certificate:AzureKeyVault:CerPath` | string | `""` | `Signing__Certificate__AzureKeyVault__CerPath` | **REQUIRED a menos que `Blob` esteja definido** — exatamente um dos dois. Caminho para o `.cer` que contém o certificado público de `KeyName`. O boot falha se sua chave pública não corresponder à chave do cofre. |
### `…:Blob` — lendo o arquivo do Azure Blob Storage
Um host **sem disco local durável** — um container, um App Service, um pod do AKS — não tem onde
guardar um `.pfx` ou um `.cer`. As duas origens que apontam para um arquivo podem, em vez disso,
apontar para um blob.
Disponível em **`Pfx`** (com o `.pfx`, em vez de `Path`) e em **`AzureKeyVault`** (com o `.cer`, em vez
de `CerPath`), no bloco legado e em cada entrada de `Signing:Profiles[]`. Exatamente um entre o caminho
local e este bloco; **os dois definidos, ou nenhum, é recusado no boot.** Nada aqui é herdado da
credencial `AzureKeyVault` ao lado nem de `Storage:AzureFiles`.
Substitua `` abaixo por `Pfx` ou `AzureKeyVault`.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Signing:Certificate::Blob:Url` | string | *(não definido)* | `Signing__Certificate____Blob__Url` | **REQUIRED quando o bloco está presente.** URL completa do blob, por exemplo `https://contoso.blob.core.windows.net/certificates/signer.cer`. Precisa ser uma URL `https://` absoluta, precisa indicar um container *e* um blob, e **não pode ter query string** — isso seria uma shared access signature, que não é uma credencial aceita. Por causa dessa regra, a URL nunca é secreta e é exibida no banner de inicialização. Qualquer host é aceito, então nuvens soberanas não precisam de chave extra. |
| `Signing:Certificate::Blob:Credential` | enum | *(não definido)* | `Signing__Certificate____Blob__Credential` | **REQUIRED quando o bloco está presente.** Um de `ManagedIdentity`, `ServicePrincipal`, `AccountKey`. **Não tem valor padrão** — usar silenciosamente a identidade Azure do próprio host não é uma decisão que o produto toma por você. |
| `Signing:Certificate::Blob:TenantId` | string | *(não definido)* | `Signing__Certificate____Blob__TenantId` | **REQUIRED para `ServicePrincipal`.** Obrigatório mesmo quando o `AppId` coincide com o do bloco `AzureKeyVault` — esse bloco não tem chave de tenant, e nada é herdado. |
| `Signing:Certificate::Blob:AppId` | string | *(não definido)* | `Signing__Certificate____Blob__AppId` | **REQUIRED para `ServicePrincipal`.** Precisa de **Storage Blob Data Reader** no container. |
| `Signing:Certificate::Blob:AppSecret` | string | *(não definido)* | `Signing__Certificate____Blob__AppSecret` | **REQUIRED para `ServicePrincipal`, SECRET.** Forma por variável de ambiente recomendada. |
| `Signing:Certificate::Blob:AccountKey` | string | *(não definido)* | `Signing__Certificate____Blob__AccountKey` | **REQUIRED para `AccountKey`, SECRET.** Concede acesso total ao plano de dados da conta inteira e não pode ter o escopo restringido; gera um aviso na inicialização. |
> **Perigo: Com Pfx, o blob é a chave de assinatura**
>
> Uma chave de conta concede acesso total ao plano de dados da conta de armazenamento inteira, não pode
> ter o escopo restringido e não expira. Com `AzureKeyVault`, o blob contém um `.cer` — material público.
> Com `Pfx`, ele contém um arquivo PKCS#12, então **uma chave de conta vazada equivale à sua chave de
> assinatura.** Prefira `ManagedIdentity`, ou `ServicePrincipal` quando o host conseguir acessar um
> tenant.
O arquivo é lido **uma vez, no boot** — um blob renovado exige reiniciar o serviço, exatamente como um
arquivo local renovado. As violações das regras de formato acima (os dois ou nenhum, `Url` ou
`Credential` ausente, uma query string) são recusadas no boot, mas um **blob inacessível ou ilegível
deixa o perfil degradado** e o host rodando: o perfil é identificado no banner de inicialização, no log
durável e no `/api/ready`, os jobs roteados para ele falham com `profile.degraded`, e todos os outros
perfis continuam assinando. Veja [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#lendo-o-arquivo-de-um-blob).
> **Aviso: Mudou na 2.1.0 — um certificado que não abre não impede mais o host de iniciar**
>
> Até a 2.0.x, um único perfil cujo certificado não carregasse — um caminho digitado errado, uma senha
> errada, um blob ou cofre inacessível — impedia o boot inteiro. Os perfis agora são editados pelo
> dashboard, e uma página servida por um host que se recusa a iniciar não serve como caminho de
> recuperação; por isso, somente esse perfil fica **degradado**. Corrija o certificado e **reinicie**:
> uma chave é aberta uma vez na inicialização e nunca é recarregada.
Veja [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates) para os comandos de descoberta de thumbprint, o passo a passo de
configuração no Azure e um detalhamento de cada origem.
## `Signing:Profiles[]` — perfis de assinatura por pasta
Um **perfil de assinatura** agrupa sob um nome todas as decisões por pasta (formato, certificado,
verificação, criptografia, validação de certificado, aprovação). Cada perfil escolhe a pasta monitorada
que o alimenta, **uma pasta por perfil**; o `Storage:Inputs[].Profile` é apenas o dado do seed (carga
inicial) para essa escolha (veja [abaixo](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#storageinputsprofile--roteamento-por-pasta)).
> **Aviso: Mudou na 2.1.0 — esta seção é um seed de uso único, não a fonte da verdade**
>
> Os perfis de assinatura ficam no **banco operacional**. No **primeiro boot com uma tabela de perfis
> vazia**, as chaves abaixo são importadas como linhas; daí em diante, o banco é a referência e **esta
> seção é ignorada**. Nada é mesclado, e editá-la depois desse primeiro boot não tem efeito — o log de
> inicialização avisa isso, indicando a seção, a cada boot que a encontra ainda preenchida. Mantenha-a se
> quiser que uma implantação nova já suba configurada; remova-a quando os perfis estiverem no banco.
>
> Daí em diante, um perfil é consultado, **criado** e editado na página **Perfis de assinatura** do
> dashboard — o comportamento (o formato e as opções de ligar/desligar abaixo), a pasta de entrada, a
> regra de aprovação e o certificado —, e é **desabilitado** ali, em vez de apagado. É também nessa página
> que se confere, depois do primeiro boot, o que o seed importou. Veja [Dashboard](https://docs.lacunasoftware.com/articles/bulk-signer/dashboard).
**Uma mudança em um perfil armazenado entra em vigor sem reinicialização — exceto a do certificado.**
Toda escrita de perfil é percebida pelo pipeline no polling que ele já faz, então um perfil editado vale
para o próximo job capturado — formato, `Verify`, `Encrypt`, `CheckCNAB240`, `CheckCnab240PaymentDates`,
`SimpleP7s`, a pasta de entrada e toda a regra de aprovação — em até um `Pipeline:PollIntervalSeconds`,
em todas as instâncias. Uma mudança de **certificado** é gravada na hora, mas exige **reinicialização**:
um perfil mantém aberto um handle de chave privada, e trocar a chave de um job que está no meio de uma
assinatura não é um risco que valha a pena correr por um campo que muda mais ou menos uma vez por ano.
Até a reinicialização, o perfil fica marcado como aguardando reinício, e a lista de perfis, o banner e a
origem registrada em cada job continuam indicando o certificado de fato em vigor — o que mantém o
registro de auditoria de um job fiel ao que de fato o assinou.
Há dois modos de configuração suportados, e ambos descrevem o que vai para o **seed**:
- **Modo legado** (padrão — `Signing:Profiles[]` omitido ou vazio). Um perfil chamado `default` é
derivado do bloco `Signing:Certificate` existente mais o `Encryption:Enabled` e gravado como uma linha
real. Nenhuma mudança de configuração é necessária para uma instalação simples, com um único
certificado.
- **Modo de perfis** (declare `Signing:Profiles[]`). Cada entrada é um perfil nomeado, com certificado e
postura próprios. O `Signing:Certificate` é ignorado. Cada entrada é validada como se fosse o bloco
global de certificado — as mesmas regras de `Pfx` / `Pkcs11` / `WindowsStore` / `AzureKeyVault` se
aplicam a cada perfil.
**Um perfil com segredo precisa do
[`Signing:ProfileSecretsKey`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofilesecretskey--a-chave-que-criptografa-os-segredos-dos-perfis-armazenados)
definido antes desse primeiro boot.** Uma senha de PKCS#12, um segredo de aplicativo do Azure Key Vault
ou uma credencial de blob de material de assinatura é criptografado em repouso, e a importação é
recusada em vez de gravá-lo em texto claro.
As regras da tabela abaixo são impostas **sempre que um perfil muda**: no seed, para uma entrada
declarada aqui, e nos formulários do dashboard, para um perfil criado ou editado ali. Um perfil que já
está no banco nunca é revalidado no boot, então uma regra que ficou mais rígida em uma atualização
aparece como um aviso ou um perfil degradado, e não como um host que não inicia.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Signing:Profiles[].Name` | string | n/a | `Signing__Profiles__0__Name` | **REQUIRED.** Mesma regex dos nomes de pasta: `^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$`. Único na lista. É por esse nome que o `Storage:Inputs[].Profile` do seed e o `?profile=` de um upload REST se referem ao perfil, e ele aparece em labels de métricas, chips do dashboard e mensagens de auditoria. |
| `Signing:Profiles[].Enabled` | bool | `true` | `Signing__Profiles__0__Enabled` | Se novos arquivos podem ser roteados para este perfil. Um perfil é **desabilitado, nunca apagado**, para que os jobs históricos continuem apontando para uma regra real e os jobs já enfileirados para um perfil desabilitado rodem até o fim. Imposto no ponto de entrada dos arquivos: uma pasta monitorada, um upload, uma nova varredura e uma nova tentativa são todos recusados com `profile.disabled`. Definido na página do perfil depois do primeiro boot; desabilitar ali é **recusado enquanto o perfil é alimentado por uma pasta monitorada**, que é indicada — remova a pasta no mesmo salvamento ou passe-a antes para outro perfil. O `default` não pode ser desabilitado de forma alguma — `Enabled: false` nele causa uma recusa de boot, já que é para ele que vai um upload que não indica perfil. |
| `Signing:Profiles[].Format` | enum | n/a | `Signing__Profiles__0__Format` | **REQUIRED** para todo perfil, exceto o `default`: `Pades`, `Cades` ou `Xades`. Somente o `default` — o perfil para onde vai um upload que não indica perfil — pode deixá-lo sem definir; nesse caso, o formato é detectado arquivo a arquivo, pela extensão. |
| `Signing:Profiles[].Method` | enum | `Local` | `Signing__Profiles__0__Method` | `Local` (assinar com o certificado local configurado), `LacunaSigner` (encaminhar ao Lacuna Signer para um participante humano) ou `SignerFolder` (*novo na 2.16.0* — assinar, com o certificado do próprio perfil, documentos que uma pessoa criou em uma pasta vinculada do Lacuna Signer; a direção oposta). Um perfil `LacunaSigner` ou `SignerFolder` é recusado em um host sem a seção [`Signer`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signer--conexão-com-o-lacuna-signer). Veja [Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer) para os dois métodos do Signer. |
| `Signing:Profiles[].Verify` | bool | `true` | `Signing__Profiles__0__Verify` | Quando falso, o worker não faz a verificação pós-assinatura. O banner de inicialização emite um aviso, para que a postura de baixa confiança fique visível ao operador, e desligá-la pelo dashboard pede confirmação. |
| `Signing:Profiles[].Encrypt` | bool | `false` | `Signing__Profiles__0__Encrypt` | Quando verdadeiro, o worker criptografa a saída assinada com AES-256-GCM. Exige `Encryption:Enabled = true` (o validador recusa a combinação inválida na inicialização). |
| `Signing:Profiles[].ValidateCertificate` | bool | `true` | `Signing__Profiles__0__ValidateCertificate` | Quando falso, o worker não faz a verificação de cadeia / revogação do certificado antes de assinar. O banner de inicialização emite um aviso. O perfil `default` derivado no modo legado usa `false`, preservando o comportamento das instalações anteriores aos perfis. **Precisa ser `false` quando `Method = LacunaSigner`** — não há certificado local a validar. |
| `Signing:Profiles[].PreserveFileExtension` | bool | `false` | `Signing__Profiles__0__PreserveFileExtension` | Quando verdadeiro, a saída assinada mantém a extensão do arquivo original usando o infixo `.signed` no estilo PAdES: o CAdES grava `remessa.signed.rem` em vez de `remessa.rem.p7m`; o XAdES grava `nota.signed.nfe` em vez de `nota.signed.xml`. **Válido somente quando `Format = Cades` ou `Xades`** — a saída PAdES já preserva o `.pdf`, então o validador recusa a flag nesse caso. Use quando um sistema posterior no fluxo (um banco que recebe remessas assinadas, por exemplo) exigir a extensão original. |
| `Signing:Profiles[].SaveAsPem` | bool | `false` | `Signing__Profiles__0__SaveAsPem` | Quando verdadeiro, a assinatura CAdES é gravada codificada em PEM (com o delimitador `-----BEGIN PKCS7-----`) em vez de DER puro, e o nome de saída passa a ser `.pem` em vez de `.p7m`. **Válido somente quando `Format = Cades`.** A verificação sempre é feita sobre os bytes DER, antes da codificação PEM; com `Encrypt = true`, o envelope BSENC envolve o texto PEM. Pode ser combinado com `PreserveFileExtension`; nesse caso, o nome segue essa flag, e apenas o conteúdo é PEM. |
| `Signing:Profiles[].SimpleP7s` | bool | `false` | `Signing__Profiles__0__SimpleP7s` | *Novo na 2.17.0.* Quando verdadeiro, o perfil assina um **P7S simples** em vez da ADR-Básica da ICP-Brasil: um CMS anexado comum, com **SHA-1**, **sem LCRs** no envelope, **sem o atributo signing-certificate** e **sem política de assinatura** — para um destinatário cujo sistema não lê nada mais novo. A cadeia continua incluída, o nome de saída continua o do CAdES (`.p7m`, ou como `PreserveFileExtension` / `SaveAsPem` o definirem), e com `Verify = true` a saída continua sendo verificada, com uma política de validação compatível com a assinatura, e não com a da ICP-Brasil, que recusa uma assinatura sem política. O certificado é validado contra as mesmas ACs raiz que a ADR-Básica exigiria e precisa ser **RSA** (outro tipo de chave é recusado na assinatura, com uma mensagem que diz isso). **Válido somente quando `Format = Cades` e `Method = Local`, e recusado quando `Certificate:Source = AzureKeyVault`**, que nunca assina SHA-1. Ao lado de um conjunto `Approval.Signers` que inclui aprovadores, os aprovadores também assinam um P7S simples (*2.18.0*) — toda assinatura de um job é feita sob a política que o job congelou ao ficar retido —, então os certificados deles também precisam ser RSA. Uma linha armazenada que as regras recusariam assina ADR-Básica, e todas as superfícies informam a política em vigor. O SHA-1 é um resumo obsoleto: escolha-o só para um destinatário que precise dele. Ligá-lo pelo dashboard pede confirmação antes, e o banner de inicialização o indica como `policy=simple-p7s (SHA-1)`. Veja [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#um-p7s-simples-em-vez-da-adr-básica). |
| `Signing:Profiles[].CheckCNAB240` | bool | `false` | `Signing__Profiles__0__CheckCNAB240` | Quando verdadeiro, todo arquivo roteado por este perfil é interpretado e validado como uma **remessa** CNAB240 do Banco do Brasil antes de ser assinado. Um arquivo fora do padrão nunca chega ao assinador: o job vai para `Failed` com `ErrorMessage = cnab240.invalid`, a cópia preparada é movida para a pasta de erro, e as violações são registradas no histórico do job e como um evento operacional `Cnab240ValidationFailed`. Aplica-se tanto a `Local` quanto a `LacunaSigner`. A validação é apenas estrutural — veja [Arquivos de pagamento CNAB240](https://docs.lacunasoftware.com/articles/bulk-signer/cnab240). A correspondência do nome da chave não diferencia maiúsculas de minúsculas, então `CheckCnab240` também funciona. |
| `Signing:Profiles[].CheckCnab240PaymentDates` | bool | `true` | `Signing__Profiles__0__CheckCnab240PaymentDates` | *Novo na 2.15.0.* Lido somente quando `CheckCNAB240` é verdadeiro. Quando verdadeiro (o padrão), uma remessa cuja *Data do Pagamento* mais antiga já passou é recusada na chamada de assinatura com `ErrorMessage = cnab240.payment-date-passed`. Quando falso, esse arquivo é assinado, e o histórico do job e um evento operacional `Cnab240PaymentDateCheckSkipped` registram que ele estava vencido — para um banco que processa pagamentos com data passada no próximo dia útil. A validação estrutural não é afetada. Lido no momento da assinatura e nunca congelado no job, então uma mudança vale já para a próxima assinatura, sem reinicialização, inclusive a de um job retido. Veja [Arquivos de pagamento CNAB240](https://docs.lacunasoftware.com/articles/bulk-signer/cnab240#datas-de-pagamento-que-já-passaram). |
| `Signing:Profiles[].Approval` | aninhado | ausente | `Signing__Profiles__0__Approval__…` | Opcional. Se presente, os jobs deste perfil ficam retidos em `AwaitingApproval` antes de existir qualquer assinatura. **Válido somente junto com `CheckCNAB240 = true`** — recusado no seed e, depois, recusado nos dois sentidos na página do perfil (não é possível adicionar uma regra de aprovação com a verificação desligada, nem desligar a verificação enquanto houver uma regra). Um job que, mesmo assim, chegue à etapa de aprovação sem a interpretação falha com `approval.content-unmeasured` em vez de ficar retido. Veja abaixo. |
| `Signing:Profiles[].Certificate.*` | aninhado | n/a | `Signing__Profiles__0__Certificate__…` | **REQUIRED quando `Method = Local`**, a menos que o perfil seja sem chave (`Approval.Signers = Approvers`, abaixo). Mesmo formato do bloco global `Signing:Certificate`. Um erro de formato em qualquer entrada — uma chave ausente, um caminho e um blob ao mesmo tempo — impede a inicialização com um erro agregado; um certificado bem formado que **não abre** deixa esse perfil degradado e o host rodando. **Recusado quando `Method = LacunaSigner`.** |
| `Signing:Profiles[].Signer.Name` | string | n/a | `Signing__Profiles__0__Signer__Name` | **REQUIRED quando `Method = LacunaSigner`.** Nome de exibição do participante para quem o Lacuna Signer enviará o documento. |
| `Signing:Profiles[].Signer.Email` | string | n/a | `Signing__Profiles__0__Signer__Email` | **REQUIRED quando `Method = LacunaSigner`.** E-mail do participante — precisa conter `@`. |
| `Signing:Profiles[].Signer.Identifier` | string | n/a | `Signing__Profiles__0__Signer__Identifier` | **REQUIRED quando `Method = LacunaSigner`.** Identificador nacional do participante (CPF no Brasil). |
| `Signing:Profiles[].SignerFolder.Id` | string (GUID) | n/a | `Signing__Profiles__0__SignerFolder__Id` | *Novo na 2.16.0.* **REQUIRED quando `Method = SignerFolder`**, ignorado em qualquer outro método. O id da pasta do Lacuna Signer cujos documentos este perfil assina — a própria pasta, nunca as subpastas —, copiado da pasta no Signer. **Único entre os perfis**: duas entradas com a mesma pasta impedem o boot, citando o primeiro dono. O `SignerFolder.Name`, opcional, serve só para exibição — é o que a página do perfil mostra para a pasta. Veja [Assinando em uma pasta do Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#assinando-em-uma-pasta-do-lacuna-signer--method--signerfolder) abaixo. |
### `Signing:Profiles[].Approval` — a etapa de aprovação
Se presente, os jobs deste perfil param antes de existir qualquer assinatura e aguardam uma pessoa.
Válido somente junto com `CheckCNAB240 = true` — um aprovador que não pode ver o valor não está
aprovando nada de fato, e o validador recusa a combinação na inicialização. Aplica-se tanto a `Local`
quanto a `LacunaSigner`. Passo a passo completo: [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals).
Como o resto do perfil, o bloco abaixo é dado de seed: depois do primeiro boot, o pool, o quórum, o
prazo de espera e o conjunto de assinantes são editados na página do perfil, e a mudança vale para o
próximo job que ficar retido. Um job já retido mantém a regra congelada nele no momento da retenção,
então editar um pool nunca autoriza nada retroativamente. Remover alguém de um pool revoga
imediatamente o link de aprovador dessa pessoa.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `…Approval.MinimumApprovers` | int | `1` | `Signing__Profiles__0__Approval__MinimumApprovers` | O **quórum**: quantos membros distintos do pool precisam aprovar. No mínimo 1 e não maior que o pool — um quórum maior que o pool nunca pode ser atingido, então todo job ficaria retido para sempre; por isso, o validador o recusa. |
| `…Approval.ExpiresAfter` | TimeSpan | ausente | `Signing__Profiles__0__Approval__ExpiresAfter` | Prazo de espera opcional no formato `d.hh:mm:ss` — `"2.00:00:00"` são quarenta e oito horas. Precisa ser positivo. Um job retido por mais tempo é **cancelado** com o motivo `Approval window expired.`, e a cópia preparada dele é movida para `error/`. Ausente (o padrão), um job retido espera indefinidamente. A janela é medida com base no prazo congelado no job no momento da retenção. É um temporizador de manutenção, não um controle sobre pagamentos vencidos — quem recusa uma remessa cujas datas já passaram é a verificação da data de pagamento. |
| `…Approval.Signers` | string | `ProfileKey` | `Signing__Profiles__0__Approval__Signers` | O **conjunto de assinantes**: de quem são as assinaturas presentes na saída de um job deste perfil — `ProfileKey` (o próprio certificado do perfil, como antes), `Approvers` (cada pessoa que aprova coassina com um certificado próprio) ou `ProfileKeyAndApprovers` (ambos). Um erro de digitação é recusado com a indicação dos três valores. Congelado no job junto com o resto da regra quando ele fica retido. Os dois conjuntos que incluem aprovadores precisam de um **meio de assinatura** neste host — um [`WebPki:License`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#webpki--lacuna-web-pki-no-navegador-do-aprovador) para um certificado no navegador do aprovador, ou o [`CloudHub`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#cloudhub--lacuna-cloudhub-para-certificados-em-nuvem) para um certificado mantido por um provedor em nuvem, e qualquer um dos dois basta — e de uma forma de identificar o aprovador (`ApproverPortal:Enabled` ou uma seção `Auth:EntraId`); são recusados quando falta qualquer um dos dois, e também com qualquer `Format` diferente de `Cades`, já que a assinatura de um aprovador é uma coassinatura CAdES. Além disso, `ProfileKeyAndApprovers` é recusado junto com `Method = LacunaSigner`. Com `Approvers`, o perfil é **sem chave**: o bloco `Certificate` dele não é exigido nem validado, nada é aberto para ele na inicialização, e ele não fica degradado por não ter certificado. Veja [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals). |
| `…Approval.Approvers[]` | array | `[]` | `Signing__Profiles__0__Approval__Approvers__0__…` | **REQUIRED e não vazio** quando `Approval` está presente. O **pool** de pessoas autorizadas a aprovar — *não* uma lista de pessoas que precisam, todas, aprovar. Com três entradas e `MinimumApprovers: 1`, nenhuma pessoa específica é obrigatória. |
| `…Approval.Approvers[].Name` | string | n/a | `…__Approvers__0__Name` | **REQUIRED.** Nome de exibição. É o que o registro de auditoria mostra para este aprovador. |
| `…Approval.Approvers[].Email` | string | n/a | `…__Approvers__0__Email` | **REQUIRED**, precisa conter `@` e ser único dentro do pool (sem diferenciar maiúsculas de minúsculas). Um duplicado permitiria que uma mesma pessoa ocupasse duas vagas do pool e atingisse sozinha um quórum de dois. Mascarado na narração do console e nos logs duráveis; armazenado por inteiro no snapshot de aprovação do job. |
| `…Approval.Approvers[].Cpf` | string | n/a | `…__Approvers__0__Cpf` | **REQUIRED.** Onze dígitos, com ou sem pontuação (`123.456.789-09` e `12345678909` são ambos aceitos). Os dígitos verificadores são validados na inicialização — um erro de digitação identifica outra pessoa, e a linha de auditoria resultante parece tão legítima quanto uma correta. Serve somente para exibição e auditoria: nenhuma lógica depende dele. Mascarado nos logs duráveis. |
> **Aviso: Escreva ExpiresAfter com o componente de dias**
>
> Um valor de três componentes é `hh:mm:ss` somente enquanto o primeiro número for 23 ou menos; a partir
> de 24, o .NET o lê como **dias**, então `"48:00:00"` é interpretado como quarenta e oito *dias* e passa
> na verificação de duração positiva. O valor não é recusado — uma janela longa pode ser intencional —,
> mas o **banner de inicialização avisa a partir de 24 dias**, citando tanto o valor resolvido
> (`expires=1152h`) quanto a grafia que o corrige. O boot é o único momento em que isso pode ser
> detectado.
#### Exemplo: um perfil de pagamentos que retém para aprovação
```json
{
"Name": "pagamentos-bb",
"Format": "Cades",
"Method": "Local",
"CheckCNAB240": true,
"Certificate": {
"Source": "Pfx",
"Pfx": { "Path": "/etc/bulksigner/pagamentos.pfx", "Password": "" }
},
"Approval": {
"MinimumApprovers": 2,
"ExpiresAfter": "2.00:00:00",
"Approvers": [
{ "Name": "Maria Silva", "Email": "maria@empresa.com.br", "Cpf": "12345678909" },
{ "Name": "João Souza", "Email": "joao@empresa.com.br", "Cpf": "111.444.777-35" },
{ "Name": "Ana Ferreira", "Email": "ana@empresa.com.br", "Cpf": "52998224725" }
]
}
}
```
O seed recusa, antes que o primeiro job rode — e, depois, a página do perfil recusa ao salvar: um bloco
`Approval` sem `CheckCNAB240`; um pool vazio; um `MinimumApprovers` abaixo de 1 ou maior que o pool; um
e-mail malformado, ou o mesmo e-mail duas vezes; um CPF cujos dígitos verificadores não conferem; um
`ExpiresAfter` não positivo; um conjunto de assinantes com aprovadores em um host sem meio de assinatura
ou sem forma de identificar um aprovador.
> **Perigo: A página de aprovação por job é anônima**
>
> A etapa de aprovação é real — um job de fato não é assinado até que pessoas suficientes aprovem —, mas
> o `/approve/{jobId}` não exige credencial: qualquer pessoa que consiga acessar o link pode aprovar *ou
> rejeitar* em nome de qualquer membro do pool do job. O banner de inicialização emite um aviso para todo
> perfil com aprovação configurada, a cada boot. Habilite o [`ApproverPortal`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#approverportal) ou o
> [login pelo Entra ID](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#authentraid--login-opcional-pelo-microsoft-entra-id) para restringir isso, e
> leia [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals#segurança) antes de expor o host a uma rede acessível pelos navegadores
> dos aprovadores.
### Assinando em uma pasta do Lacuna Signer — `Method = SignerFolder`
> **Dica: Novo na 2.16.0**
>
> Um terceiro método de assinatura. O passo a passo para o operador está em
> [Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#assinando-em-uma-pasta-do-lacuna-signer--signerfolder).
Um perfil neste método assina documentos que uma pessoa cria **no Lacuna Signer**, na pasta indicada
pelo `SignerFolder.Id` dele, com o titular do certificado do perfil como participante. Este host os
encontra por uma varredura periódica ([`Signer:FolderSweepIntervalSeconds`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signer--conexão-com-o-lacuna-signer))
e, quando o [`Signer:WebhookSecret`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signer--conexão-com-o-lacuna-signer) está definido, segundos depois
de uma notificação do Signer; ele assina o hash entregue pelo Signer com a chave do próprio perfil, e o
Signer incorpora a assinatura ao documento e o guarda — nada entra em `input/` e nada chega a `output/`.
O titular é associado pelo **CPF que o certificado traz** (certificados e-CPF e e-CNPJ trazem um), nunca
por um campo digitado. O que o seed recusa — o mesmo que um salvamento pelo dashboard recusa
([Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#assinando-em-uma-pasta-do-lacuna-signer--signerfolder)) — é:
- **Nenhum `SignerFolder.Id`, ou um que não seja um GUID** — o Signer identifica uma pasta pelo id;
copie-o da pasta no Signer.
- **Uma pasta a que outra entrada já está vinculada** — duas entradas com o mesmo `SignerFolder.Id` fazem
a segunda ser recusada, citando a primeira; os ids são comparados como GUIDs, não como texto.
- **O perfil `default`** — é para ele que vão os uploads sem perfil e as pastas monitoradas sem vínculo, e
este método não recebe arquivos.
- **Nenhuma configuração `Signer:*` neste host** — os documentos são encontrados e assinados com o
`Signer:Endpoint` e a `Signer:ApiKey` deste host.
- **`Verify`, `Encrypt`, `ValidateCertificate` ou `CheckCnab240` iguais a `true`** — o Signer guarda o
documento, escolhe o contêiner e valida o certificado no início da assinatura, e nunca entrega um
arquivo a este host. Note que `Verify` e `ValidateCertificate` têm padrão `true`; por isso, uma entrada
`SignerFolder` declara as duas como `false`.
- **Um bloco `Approval`** — o fluxo do documento é decidido no Signer.
- **Uma pasta monitorada vinculada a ele** — um `Storage:Inputs[].Profile` que cita o perfil. Os
documentos dele nascem no Signer, então nenhum arquivo é lido para ele.
- **Uma origem de certificado por host com `Cluster:Enabled`**, como para qualquer perfil. Todas as
outras origens servem, inclusive um PKCS#12 enviado e o Azure Key Vault.
Um certificado **sem CPF** não está entre elas: o seed não abre nenhum certificado, então um perfil assim
sobe e é informado como **degradado** — assim como um perfil armazenado em um host que depois perdeu as
configurações `Signer:*`, e um cuja pasta o Signer depois diz não existir
([Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#quando-o-próprio-perfil-não-consegue-trabalhar)).
O `Format` é **ignorado**: o Signer escolhe PAdES para um PDF, XAdES para XML e CAdES para os demais, e o
job registra qual. Em um PDF, a aplicação da política ICP-Brasil a essa assinatura PAdES depende da
configuração `UseBrazilianPdfSigningPolicies` da organização no Signer, e não do Bulk Signer — o padrão
ADR-Básica do produto não a alcança.
```json
{
"Signer": {
"Endpoint": "https://signer.lacunasoftware.com",
"FolderSweepIntervalSeconds": 300
},
"Signing": {
"Profiles": [
{
"Name": "contratos",
"Method": "SignerFolder",
"SignerFolder": { "Id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" },
"Verify": false,
"ValidateCertificate": false,
"Certificate": {
"Source": "Pfx",
"Pfx": { "Path": "/etc/bulksigner/certificates/empresa-ecnpj.pfx" }
}
}
]
}
}
```
com `Signer__ApiKey` e `Signing__Profiles__0__Certificate__Pfx__Password` fornecidas pelo ambiente.
### `Storage:Inputs[].Profile` — roteamento por pasta
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Storage:Inputs[].Profile` | string? | `null` (→ "default") | `Storage__Inputs__0__Profile` | Opcional, e **dado de seed**: lida uma única vez, no primeiro boot com uma tabela de perfis vazia, e aplicada ao perfil que ela indica — a linha do perfil passa então a guardar a pasta, uma pasta por perfil, e a chave é ignorada nos boots seguintes. Nulo ou vazio vincula ao perfil `default`. Uma pasta que o seed não consegue vincular — o perfil dela não está em `Signing:Profiles[]`, ou uma pasta anterior já foi vinculada a esse perfil — fica **sem atribuição**, é informada na inicialização e aparece assim na página **Pastas de entrada** e no `/api/ready`; os arquivos dela esperam até que um perfil a escolha. O host inicia de qualquer forma. |
> **Aviso: Mudou na 2.2.0 — o perfil escolhe a pasta**
>
> Até a 2.1.x, era esta chave que roteava uma pasta, e ela era lida a cada boot. Agora o vínculo fica na
> linha do perfil no banco operacional. Depois do primeiro boot, uma pasta é roteada, movida ou
> desvinculada na página do perfil (**Pasta de entrada**, em **Editar comportamento**) e **somente ali**:
> editar esta chave não muda nada, e o log de inicialização avisa isso com uma linha de aviso que conta as
> chaves de pasta ignoradas. Uma mudança entra em vigor no próximo arquivo, em todas as instâncias, sem
> reinicialização. Mantenha a chave se quiser que uma implantação nova já suba roteada; apague-a quando os
> perfis estiverem no banco, e essa linha desaparece.
**Nada mais em `Storage:Inputs[]` mudou de lugar.** `Name`, `Path`, `Provider`, `AzureFiles`,
`PollIntervalSeconds` e as duas listas de itens ignorados são configuração do host: lidas a cada boot,
validadas pelas regras de
[`Storage:Inputs[]`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#storageinputs--regras-de-validação-impostas-na-inicialização) exatamente como
antes, e não editáveis em nenhum lugar da interface web. A única chave que saiu nunca foi uma
característica da pasta — ela dizia qual regra de assinatura os arquivos da pasta recebem, e quem deve
dizer isso é o perfil.
Uma pasta que nenhum perfil escolheu fica **sem atribuição**: o observador dela não enfileira nada, uma
nova varredura a ignora e avisa isso, e os arquivos dela ficam esperando. Um perfil vinculado a uma pasta
que este host não configura mais é informado no banner de inicialização, como uma linha
`profile-input-folder:` no `/api/ready` que não reprova o probe, e como um alerta na página de perfis do
dashboard.
### Exemplo: três perfis roteados por pasta, um por origem de certificado
Os perfis são independentes, então uma única implantação pode misturar modelos de custódia de chave — um
HSM para NF-e, um PFX em disco para contratos e uma chave mantida em cofre para faturas —, com cada pasta
monitorada alimentando o perfil de que precisa. A escolha da pasta passa a ser feita em cada perfil, na
página dele, depois do primeiro boot; a chave `Profile` de cada entrada de `Storage:Inputs[]` define essa
escolha no seed, uma única vez, no mesmo boot que importa estes três perfis.
```json
"Signing": {
"PkiSdkLicense": "",
"Profiles": [
{
"Name": "nfe",
"Format": "Xades",
"Verify": true,
"Encrypt": false,
"ValidateCertificate": true,
"Certificate": {
"Source": "Pkcs11",
"Pkcs11": { "ModulePath": "/usr/lib/x86_64-linux-gnu/pkcs11/libsofthsm2.so", "Thumbprint": "...", "PinEnvVar": "BULK_SIGNER_PKCS11_PIN" }
}
},
{
"Name": "contracts",
"Format": "Pades",
"Verify": true,
"Encrypt": true,
"ValidateCertificate": true,
"Certificate": {
"Source": "Pfx",
"Pfx": { "Path": "/etc/bulksigner/contracts.pfx", "Password": "" }
}
},
{
"Name": "invoices",
"Format": "Pades",
"Verify": true,
"Encrypt": false,
"ValidateCertificate": true,
"Certificate": {
"Source": "AzureKeyVault",
"AzureKeyVault": {
"Endpoint": "https://my-vault.vault.azure.net/",
"AppId": "8f2c1b3e-1111-2222-3333-444455556666",
"AppSecret": "",
"KeyName": "bulk-signer-invoices-key",
"CerPath": "/etc/bulksigner/certificates/invoices.cer"
}
}
}
]
},
"Storage": {
"Root": "/var/lib/bulksigner",
"Inputs": [
{ "Name": "nfe-incoming", "Path": "/var/lib/bulksigner/input-nfe", "Profile": "nfe" },
{ "Name": "contracts-incoming", "Path": "/var/lib/bulksigner/input-contracts", "Profile": "contracts" },
{ "Name": "invoices-incoming", "Path": "/var/lib/bulksigner/input-invoices", "Profile": "invoices" }
]
}
```
O perfil `invoices` deixa o `AppSecret` vazio no arquivo e o obtém do ambiente. Elementos de array são
vinculados por **índice posicional**, então o segredo do terceiro perfil é:
```bash
export Signing__Profiles__2__Certificate__AzureKeyVault__AppSecret='…'
```
Como esse perfil tem um segredo, o primeiro boot também precisa de `Signing__ProfileSecretsKey`
definida — a importação criptografa o segredo do aplicativo com ela e é recusada sem ela. Os outros dois
não têm nenhum segredo.
> **Aviso**
>
> Esse índice é posicional, não baseado em nome. Inserir um novo perfil *acima* de `invoices` o desloca
> para o índice `3`, a variável de índice `2` deixa de chegar a ele, e o seed falha com
> `Signing:Profiles[3].Certificate.AzureKeyVault.AppSecret is required`. Confira novamente todas as
> variáveis de ambiente indexadas depois de reordenar a lista. Isso só importa até o seed rodar: depois do
> primeiro boot, o segredo fica, criptografado, no perfil armazenado, e alterá-lo — por exemplo, um client
> secret rotacionado — é feito na página do perfil (**Editar certificado**), seguido de uma
> reinicialização, e não alterando a variável.
O banner de inicialização lista cada perfil resolvido com o formato, a origem do certificado e as flags
de verificação/criptografia/validação de certificado. Perfis com `Verify=false` ou
`ValidateCertificate=false` emitem avisos adicionais, para que a postura de baixa confiança fique
registrada nos logs duráveis. Um perfil cujo certificado não abriu é listado com o prefixo
`DEGRADED · ` e o motivo ao lado.
## `Signer` — conexão com o Lacuna Signer
Um tenant do Lacuna Signer por host — o endpoint e a chave de API são globais, não por perfil. O
validador **só se ativa se esta seção existir**: se ela for totalmente omitida, nada aqui é exigido, que
é o caso de uma implantação somente local. Se qualquer parte dela for definida, o bloco inteiro é
validado, porque um bloco configurado pela metade é uma implantação que só descobriria a falha no
primeiro envio ao Lacuna Signer. Veja [Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer).
> **Aviso: Mudou na 2.1.0 — a seção é avaliada por si só, não pelos perfis existentes**
>
> Até a 2.0.x, estas chaves só eram exigidas quando alguma entrada de `Signing:Profiles[]` tinha
> `Method = LacunaSigner` — uma pergunta que a configuração não consegue mais responder, já que os perfis
> ficam no banco operacional. Agora a exigência fica no perfil: **um perfil que seleciona
> `Method = LacunaSigner` é recusado — no seed ou na página do perfil — quando esta seção está ausente.**
> O mesmo fato decide se o worker de polling do assinador remoto roda, então um host com estas
> configurações está pronto para receber, depois do boot, um perfil apontado para o Lacuna Signer, sem
> reinicialização. Duas consequências na atualização: um **bloco `Signer:` preenchido pela metade agora
> impede o boot** mesmo que nada o use (a mensagem indica as duas chaves e sugere remover a seção como
> solução), e um host com o bloco inteiro definido, mas sem nenhum perfil que o use, agora roda o worker de
> polling ocioso. Remova a seção se este host assina tudo localmente.
A mesma regra vale para `Method = SignerFolder` (2.16.0): ele é recusado quando esta seção está ausente,
e são estas configurações que iniciam a varredura de pastas do Signer. Um perfil `SignerFolder`
armazenado que se encontra em um host sem elas fica degradado, em vez de impedir o boot
([Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#quando-o-próprio-perfil-não-consegue-trabalhar)).
`Endpoint` e `ApiKey` servem aos dois métodos. `PollIntervalSeconds`, `TimeoutHours` e
`MaxConsecutiveApiFailures` só são lidas para `LacunaSigner`; `FolderSweepIntervalSeconds` e
`WebhookSecret`, só para `SignerFolder`.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Signer:Endpoint` | string | `""` | `Signer__Endpoint` | **REQUIRED** quando algum perfil usa `LacunaSigner` ou `SignerFolder`. URL base da instância do Lacuna Signer. Padrão na nuvem: `https://signer.lacunasoftware.com`. Implantações *on-premises* apontam para a instância do cliente. |
| `Signer:ApiKey` | string | `""` | `Signer__ApiKey` | **REQUIRED, SECRET** quando algum perfil usa `LacunaSigner` ou `SignerFolder`. Formato esperado: `application-id\|secret`. O valor literal é removido dos logs. |
| `Signer:WebhookSecret` | string | `""` (notificações desligadas) | `Signer__WebhookSecret` | *Novo na 2.16.0.* **SECRET.** O token que o Lacuna Signer apresenta em uma **notificação** — o webhook dele, registrado nas integrações da organização no Signer com autenticação **Bearer** e este valor como token (passo a passo em [Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#notificações--o-webhook)). Enviado como `Authorization: Bearer ` e comparado em tempo constante — **nunca** como `X-API-Key`, que é a credencial REST do próprio produto. Vazio, o `POST /api/signer/notifications` responde **404** e só a varredura de pastas encontra documentos, o que custa latência e nunca um documento. Defini-lo conta como definir o bloco `Signer`, então `Signer:Endpoint` e `Signer:ApiKey` passam a ser obrigatórias. Pelo menos 32 caracteres; removido dos logs. |
| `Signer:PollIntervalSeconds` | int | `30` | `Signer__PollIntervalSeconds` | Com que frequência o worker de polling percorre cada linha `AwaitingSigner`. Faixa válida: 1–3600. |
| `Signer:TimeoutHours` | int | `168` (7 dias) | `Signer__TimeoutHours` | Quanto tempo um job pode ficar em `AwaitingSigner` antes de falhar com `code = signer.timeout`. Faixa válida: 1–8760. |
| `Signer:MaxConsecutiveApiFailures` | int | `5` | `Signer__MaxConsecutiveApiFailures` | Limite de erros transitórios consecutivos por documento antes de o worker de polling desistir desse documento. Contador em memória — reiniciar o serviço o zera. A partir da 2.16.0, um `5xx`, `408`, `429` ou a página de erro de um proxy também conta para ele — veja [Falhas de API](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#falhas-de-api-e-o-limite-por-job). |
| `Signer:FolderSweepIntervalSeconds` | int | `300` (5 minutos) | `Signer__FolderSweepIntervalSeconds` | *Novo na 2.16.0.* Com que frequência cada pasta do Lacuna Signer vinculada a um perfil `SignerFolder` habilitado é listada em busca de documentos pendentes. A varredura é o que garante que um documento seja assinado; por isso, este é o máximo que um documento espera até este host percebê-lo. Ela roda com o pipeline pausado ou não — a descoberta só enfileira jobs; a pausa impede que eles sejam reivindicados — e, com `Cluster:Enabled`, toda instância faz a varredura: um documento encontrado por duas instâncias é assumido uma vez só, porque o banco operacional registra cada par (documento, ação do fluxo) já assumido e recusa o segundo. Faixa válida: 60–86400. |
## `WebPki` — Lacuna Web PKI no navegador do aprovador
Um aprovador que coassina um arquivo de pagamento (em um perfil cujo
[conjunto de assinantes](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofilesapproval--a-etapa-de-aprovação) é `Approvers` ou
`ProfileKeyAndApprovers`) pode fazer isso com um certificado no próprio token ou no próprio repositório
de certificados, por meio da extensão de navegador Lacuna Web PKI. A biblioteca que se comunica com a
extensão acompanha o produto e nunca é carregada de uma CDN, então um aprovador em uma LAN sem acesso à
internet ainda recebe a página. O que o Web PKI precisa do host é uma **licença vinculada aos domínios
da implantação**, e é nesta seção que ela fica.
**A licença não é segredo.** Ela é enviada em texto claro ao navegador de todo aprovador, que é onde o
Web PKI a confere com o domínio da página; por isso, ela deliberadamente não é mascarada nos logs. Todos
os lugares que a informam dizem apenas se está configurada ou não, sem exibi-la: a linha
`web pki license` do banner de inicialização e a página **Sistema**.
**Ela também não causa recusa de boot.** A regra fica onde ficam todas as regras de perfil — no seed e
na página do perfil: um perfil cujo conjunto de assinantes inclui aprovadores é recusado, com a
indicação desta chave e do `CloudHub:ApiKey`, enquanto **nenhum dos dois** estiver configurado. Uma
licença (um certificado no navegador) ou o
[`CloudHub`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#cloudhub--lacuna-cloudhub-para-certificados-em-nuvem) (um certificado na nuvem) é um
*meio de assinatura*, e basta um dos dois. Uma implantação cujos conjuntos de assinantes são todos
`ProfileKey` não precisa de nenhuma seção `WebPki`. Em `localhost`, o Web PKI funciona sem licença.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `WebPki:License` | string | `""` | `WebPki__License` | Obrigatória para um perfil em que os aprovadores assinam, a menos que o `CloudHub` esteja configurado no lugar dela. Qualquer uma das duas formas que a Lacuna emite — a string binária (base64) ou o documento JSON —, repassada ao navegador sem alteração. Vinculada aos domínios da implantação: solicite-a à Lacuna para os nomes de host em que os aprovadores vão abrir a página. Deliberadamente **não** marcada como SECRET. |
```json
{
"WebPki": {
"License": ""
}
}
```
## `CloudHub` — Lacuna CloudHub para certificados em nuvem
Um aprovador cujo certificado ICP-Brasil foi emitido no HSM de um provedor — um *certificado em nuvem* —
não tem nada que um navegador consiga acessar, então o Web PKI não consegue assinar por ele. O Lacuna
CloudHub reúne esses provedores atrás de uma única API: o produto abre uma sessão para o CPF do
aprovador, envia o navegador dele ao provedor que ele escolher, recebe-o de volta no endereço desta
própria implantação e assina o arquivo servidor a servidor com o certificado que o provedor autenticou.
É esta seção que faz esse meio de assinatura existir em um host; sem chave, nenhuma página o oferece, e
a rota de retorno dele fica inacessível.
**Ele atende a um lote tão bem quanto a um arquivo.** Desde a 2.14.0, o **Aprovar N selecionados** do
portal do aprovador oferece a nuvem ao lado do navegador — e sozinha, em um host sem licença do Web PKI —,
com um único login no provedor para o lote inteiro. Nada aqui precisa ser configurado à parte para isso.
**A chave é o que liga o recurso, e ela é segredo.** Diferentemente da licença do Web PKI, o
`CloudHub:ApiKey` é uma credencial ao portador para toda sessão que este host abre, então ela é mascarada
em todos os logs. Defina-a pela variável de ambiente. O validador **só se ativa se a chave existir**: se
ela for omitida, nada aqui é exigido; se for definida, a seção inteira é validada.
**O `PublicBaseUrl` é obrigatório junto com a chave, e é configurado em vez de derivado.** O CloudHub
precisa de um endereço absoluto para onde devolver o navegador, e este produto gera todos os outros
links relativos à requisição. Derivar o endereço de headers encaminhados foi descartado: um endereço
derivado errado só é descoberto quando um aprovador fica preso no provedor, enquanto um endereço
configurado errado é recusado no boot. Informe o esquema, o host e o prefixo de caminho, se houver, em
que os aprovadores abrem o portal, sem query e sem fragmento.
**Ele não causa recusa de boot para um perfil.** Um perfil em que os aprovadores assinam precisa desta
seção ou de uma licença do Web PKI, e qualquer uma das duas basta; essa regra é verificada no seed e na
página do perfil. Um host com CloudHub e sem licença do Web PKI é uma implantação legítima em que todo
aprovador assina na nuvem. Nada testa o CloudHub no boot nem no `/api/ready`: a linha `cloudhub` do
banner de inicialização, a página **Sistema** e o `/api/ready/details` dizem apenas se está configurado
ou não, indicando o endpoint, e uma indisponibilidade do lado da Lacuna nunca altera a prontidão deste
host.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `CloudHub:ApiKey` | string | `""` | `CloudHub__ApiKey` | **SECRET.** A chave de API do CloudHub que a Lacuna emitiu para esta implantação. É ao defini-la que o meio de assinatura em nuvem passa a ser oferecido. |
| `CloudHub:Endpoint` | string | `https://cloudhub.lacunasoftware.com/` | `CloudHub__Endpoint` | Onde o CloudHub está. Omita para usar a instância pública da Lacuna. Precisa ser uma URL `http(s)` absoluta quando a chave está definida. |
| `CloudHub:PublicBaseUrl` | string | `""` | `CloudHub__PublicBaseUrl` | **REQUIRED quando `ApiKey` está definida.** O endereço absoluto pelo qual os aprovadores acessam esta implantação — esquema, host e prefixo de caminho, sem query, sem fragmento —, para onde o CloudHub devolve o navegador. Recusado no boot quando relativo ou malformado. |
```json
{
"CloudHub": {
"ApiKey": "",
"PublicBaseUrl": "https://bulksigner.example.com"
}
}
```
Os modelos de arquivo de ambiente do pacote de implantação (`deploy/linux/bulksigner.env.sample`,
`deploy/docker/.env.sample`) trazem uma linha `CloudHub__ApiKey=` comentada exatamente para isso.
## `Encryption`
Desligada por padrão. O validador só roda quando `Enabled = true`.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Encryption:Enabled` | bool | `false` | `Encryption__Enabled` | Chave geral. Quando verdadeira, o worker criptografa o artefato assinado com AES-256-GCM entre a verificação e a promoção. |
| `Encryption:Password` | string | `""` | `Encryption__Password` | **SECRET.** Senha do PBKDF2. Permitida na configuração (use `appsettings.Production.json`, que está no gitignore), mas a forma por variável de ambiente é preferível. |
| `Encryption:PasswordEnvVar` | string | `BULK_SIGNER_ENCRYPTION_PASSWORD` | `Encryption__PasswordEnvVar` | Nome da variável de ambiente que fornece a senha. Se não estiver vazia no boot, ela prevalece sobre `Encryption:Password`. |
| `Encryption:Salt` | string | `""` | `Encryption__Salt` | **REQUIRED** quando `Enabled = true`. Salt do PBKDF2 codificado em base64; decodificado, precisa ter pelo menos 16 bytes. Salts não são secretos. Mudar o salt invalida todos os envelopes anteriores. |
| `Encryption:Iterations` | int | `600000` | `Encryption__Iterations` | Contagem de iterações do PBKDF2-HMAC-SHA256. Rejeitada abaixo de `10000`. |
> **Perigo**
>
> A perda da senha é **irrecuperável** — não há endpoint de descriptografia no servidor nem custódia de
> chaves. Veja [Criptografia](https://docs.lacunasoftware.com/articles/bulk-signer/encryption).
## `Auth`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Auth:ApiKey` | string | `""` | `Auth__ApiKey` | **REQUIRED, SECRET.** Chave de API estática, com no mínimo 16 caracteres. Enviada no header `X-API-Key` por clientes programáticos; colada em `/login` por operadores para receber um cookie. Desde a 2.3.1, o `appsettings.json` distribuído não traz valor para ela, então toda implantação precisa definir a própria chave. |
| `Auth:CookieName` | string | `lbs-auth` | `Auth__CookieName` | Nome do cookie emitido por `/api/auth/login`. `SameSite=Strict`, `HttpOnly`, marcado como seguro quando a requisição foi HTTPS. |
| `Auth:ApiKeyHeader` | string | `X-API-Key` | `Auth__ApiKeyHeader` | Header HTTP lido pelo esquema de chave de API. Renomeie apenas se uma convenção de proxy reverso exigir. |
Veja [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) sobre a rotação da chave de API e o tempo de vida da sessão em cookie.
### `Auth:EntraId` — login opcional pelo Microsoft Entra ID
**Ativado pela presença — não há flag `Enabled`.** Se a seção for omitida (o padrão), tudo se comporta
exatamente como sem ela; uma implantação isolada da rede nunca precisa de um tenant da Microsoft. Se a
seção for escrita, as três chaves passam a ser obrigatórias: uma seção parcialmente preenchida **impede
o boot do host**, indicando a chave ausente. Tratar "presente, mas vazio" silenciosamente como
*desligado* é exatamente o que leva um operador a acreditar que um controle está ativo quando não está.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Auth:EntraId:TenantId` | string | *(ausente)* | `Auth__EntraId__TenantId` | **REQUIRED quando a seção está presente.** O GUID do diretório (tenant), ou um domínio verificado (por exemplo, `contoso.onmicrosoft.com`). Os pseudo-tenants multi-tenant `common` / `organizations` / `consumers` são recusados — o modo é de tenant único por design. |
| `Auth:EntraId:ClientId` | string | *(ausente)* | `Auth__EntraId__ClientId` | **REQUIRED quando a seção está presente.** O ID de aplicativo (client) do registro de aplicativo. Precisa ser um GUID válido — caso contrário, um erro de digitação aqui só apareceria no momento do login, como um erro `AADSTS` pouco claro. |
| `Auth:EntraId:ClientSecret` | string | *(ausente)* | `Auth__EntraId__ClientSecret` | **REQUIRED quando a seção está presente, SECRET.** O client secret do cliente confidencial para o fluxo de authorization code. Defina-o pela variável de ambiente; nunca o versione. |
Não há uma quarta chave. A authority, o caminho de callback, os escopos, o cookie e o tempo de vida dele
são todos derivados.
#### O que muda ao ativar o login pelo Entra ID
| Onde | Seção ausente (padrão) | Seção presente |
|------------|------------------------|----------------|
| `/login` | Formulário de chave de API | Botão **Entrar com a Microsoft**. O formulário de chave de API deixa de aparecer. |
| `POST /api/auth/login` | Troca a chave de API por um cookie | **Não emite cookie nem para uma chave correta.** Desligado, não apenas escondido. |
| Cookies de operador existentes | Válidos durante a janela deslizante de 8 horas | **Deixam de atender às políticas imediatamente.** Planeje a transição como um "desconectar todo mundo". |
| REST `X-API-Key` | Funciona | **Inalterado.** Clientes automatizados nunca percebem o modo. |
| Páginas de operador | Qualquer cookie autenticado | Exige a app role `Administrator`. |
| `/approvals` e a página de aprovação por job | Somente o link do portal do aprovador | Link **ou** uma sessão com a role `Approver`; o pool congelado continua determinando quais jobs ficam visíveis. |
| Identificação registrada na aprovação | `SelfDeclaredEmail` / `LinkDerivedEmail` | Acrescenta `EntraIdEmail` para decisões tomadas em uma sessão do Entra. |
| Sair | Limpa o cookie | Limpa somente a sessão **do Bulk Signer**. A sessão da Microsoft continua ativa, então clicar em "entrar" de novo funciona sem pedir credenciais — comportamento normal de SSO, não um defeito. |
#### As duas app roles
As roles vêm das claims de role do token — atribuições de app role e nada mais. Deliberadamente, não há
mapeamento por grupo de segurança: com ele, editar um grupo no tenant seria uma mudança de autorização
invisível. Os valores no manifesto do registro de aplicativo precisam coincidir exatamente com estas
strings:
| Valor da role | Dá acesso a | Página de destino após o login |
|---------------|------|-------------------------------|
| `Administrator` | Todas as páginas e ações de operador que o cookie de chave de API concede hoje. Sem níveis. | `/` |
| `Approver` | Somente as telas de aprovação. A role dá acesso, mas o **pool congelado ainda decide quais jobs** a pessoa vê, com base na claim de e-mail. | `/approvals` |
| *(ambas)* | Ambas. É permitido acumular as duas funções; a segregação de funções é garantida pelas verificações de role. | `/` |
| *(nenhuma)* | Nada. Uma conta que se autentica, mas não tem nenhuma role, é **recusada** em `/access-denied`. | — |
Um `returnUrl` validado sempre prevalece sobre o destino baseado em role, então os deep links continuam
funcionando.
#### Como uma pessoa conectada é identificada
**O nome registrado do operador é o UPN.** Todo evento de auditoria gravado por um `Administrator`
conectado — um perfil criado ou editado, o pipeline pausado, jobs limpos, um backup executado — traz a
claim `preferred_username` do token, e o menu do usuário mostra o mesmo valor. Usa-se o UPN em vez do
nome de exibição porque ele é único no tenant em qualquer momento, enquanto o nome de exibição é texto
livre que duas pessoas podem compartilhar. É o nome no momento da ação, não uma chave durável. Um token
sem `preferred_username` ainda consegue entrar, e os eventos dele aparecem como `(anonymous)`; o login
registra um aviso com os tipos de claim recebidos. Isso vale só para o nome do operador — os aprovadores
continuam sendo associados aos pools pelo e-mail, como descrito acima.
**Um aprovador do Entra é cumprimentado pelo nome de exibição.** A saudação do portal mostra a claim
`name` do token e, quando o tenant não a envia, o endereço de e-mail — nunca o UPN, que, para um
convidado, tem a forma `#EXT#`. Em qualquer caso, a decisão é registrada com o e-mail.
**A janela do segundo fator acompanha a sessão do tenant.** Quando
[`ApproverSecondFactor:Enabled`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#approversecondfactor) está ligado, a janela de verificação de um
aprovador do Entra é indexada pela claim `sid` do token, uma claim padrão do ID token que não exige
mudança no registro de aplicativo; quando um token não a traz, o login gera um identificador e registra
o motivo. **Sair apaga a janela da sessão**, porque a sessão do tenant sobrevive ao logout local do
produto, e o novo login silencioso de um colega na mesma estação de trabalho a herdaria.
> **Observação: Atualizando de uma versão anterior à 2.2.1**
>
> Uma sessão do dashboard aberta durante a atualização para a 2.2.1 ou posterior mantém o ticket antigo,
> que não leva a identidade do operador para dentro da página. **Saia e entre de novo uma vez** depois de
> atualizar.
#### Exemplo — configuração mínima
```json
{
"Auth": {
"ApiKey": "…",
"EntraId": {
"TenantId": "11112222-3333-4444-5555-666677778888",
"ClientId": "99990000-aaaa-bbbb-cccc-ddddeeeeffff",
"ClientSecret": ""
}
}
}
```
```bash
Auth__EntraId__ClientSecret=''
```
`Auth:ApiKey` continua obrigatória — este modo não afeta o `X-API-Key` da API REST. As três chaves do
Entra também podem ser definidas somente pelo ambiente, que é a forma natural para um container ou uma
unit do systemd. O passo a passo do registro de aplicativo está em
[Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation#login-pelo-microsoft-entra-id-opcional).
## `Storage`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Storage:Root` | string | `data` | `Storage__Root` | **REQUIRED.** Raiz na qual `processing/`, `output/`, `error/`, `db/` e `logs/` são criados. Sobrescreva conforme o alvo — `/var/lib/bulksigner` no Linux, `C:\ProgramData\Lacuna\BulkSigner\data` no Windows, `/var/lib/bulksigner` no Docker. |
| `Storage:Provider` | enum | `LocalFileSystem` | `Storage__Provider` | `LocalFileSystem` ou `AzureFiles`. Define onde fica o **compartilhamento de trabalho** — `processing/`, `output/`, `error/`. `logs/` e `db/` sempre ficam locais. Veja abaixo. |
| `Storage:Inputs[]` | array de `{Name, Path, Provider?, AzureFiles?, PollIntervalSeconds?, IgnoredExtensions?, IgnoredPrefixes?, Profile?}` | `[{Name="default", Path="{Root}/input"}]` | `Storage__Inputs__0__Name`, `Storage__Inputs__0__Path`, … | Uma ou mais pastas de entrada monitoradas. Os jobs são marcados com o `Name` da pasta e com o perfil de assinatura que escolheu a pasta; uma pasta que nenhum perfil escolheu fica *sem atribuição* e não enfileira nada. Veja abaixo as regras de validação, e [`Storage:Inputs[].Profile`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#storageinputsprofile--roteamento-por-pasta) para o que o seed faz com a única chave de roteamento. |
### `Storage:Inputs[]` — regras de validação (impostas na inicialização)
- `Name` precisa corresponder a `^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$` e ser único na lista —
somente letras minúsculas, dígitos e hifens internos, de 1 a 40 caracteres, começando e terminando com
caractere alfanumérico. Os nomes aparecem em query strings de URL, labels de métricas e na interface
do dashboard.
- `Path` não pode ser vazio e precisa resolver para um diretório que **não seja igual** ao caminho de
nenhuma outra entrada, **nem subdiretório ou diretório pai** dele. Pastas sobrepostas causariam
enfileiramento duplicado e atribuição ambígua.
- Limite recomendado: 16 entradas. Mais do que isso infla a cardinalidade das métricas e deixa a página
**Pastas de entrada** densa demais para ser útil.
- Quando `Storage:Inputs` é omitido por completo, o serviço cria uma pasta chamada `default` em
`{Storage:Root}/input`.
### `Storage:Provider` / `Storage:AzureFiles` — o compartilhamento de trabalho
Opcional, e ausente de toda implantação que mantém o armazenamento local. Definir
`Storage:Provider = AzureFiles` move o **compartilhamento de trabalho** — `processing/`, `output/` e
`error/` — para um compartilhamento do Azure Files acessado pelo SDK do próprio serviço, sem montagem
SMB e sem dependência no nível do host. O `Storage:Root` continua local e segue guardando `logs/` e
`db/`.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Storage:AzureFiles:AccountName` | string | n/a | `Storage__AzureFiles__AccountName` | **REQUIRED** quando um provider resolve para `AzureFiles`. Nome da conta sem sufixo; o endpoint é `https://.file.core.windows.net`. |
| `Storage:AzureFiles:ShareName` | string | n/a | `Storage__AzureFiles__ShareName` | **REQUIRED.** O compartilhamento de trabalho. Somente protocolo SMB — um compartilhamento NFS é recusado no boot, **com a indicação do nome**. |
| `Storage:AzureFiles:Directory` | string? | `null` (raiz do compartilhamento) | `Storage__AzureFiles__Directory` | Prefixo opcional dentro do compartilhamento, no qual `processing/`, `output/` e `error/` são criados. Permite que várias implantações usem o mesmo compartilhamento. **Somente para o compartilhamento de trabalho** — defini-lo em uma entrada de `Storage:Inputs[]` é recusado no boot, porque o diretório de uma pasta é o `Path` dela. |
| `Storage:AzureFiles:Credential` | enum | n/a | `Storage__AzureFiles__Credential` | **REQUIRED** e sem valor padrão. `ManagedIdentity`, `ServicePrincipal` ou `AccountKey`. Um bloco parcial para o modo escolhido impede o boot do host, indicando a chave ausente. |
| `Storage:AzureFiles:TenantId` / `:AppId` | string | n/a | `Storage__AzureFiles__TenantId`, `…__AppId` | Somente no modo `ServicePrincipal`. |
| `Storage:AzureFiles:AppSecret` | string | n/a | `Storage__AzureFiles__AppSecret` | **SECRET.** Somente no modo `ServicePrincipal`. Permitido na configuração; override por ambiente recomendado. |
| `Storage:AzureFiles:AccountKey` | string | n/a | `Storage__AzureFiles__AccountKey` | **SECRET.** Somente no modo `AccountKey`. **Gera aviso na inicialização**: uma chave compartilhada dá acesso total ao plano de dados da conta inteira, não pode ser restrita a um compartilhamento e nunca expira. |
O `ManagedIdentity` aceita **somente a identidade atribuída pelo sistema** — uma identidade atribuída pelo
usuário não é lida, e indicar uma causa uma falha de autenticação na primeira chamada, e não um erro de
configuração. Deliberadamente, a credencial não é `DefaultAzureCredential`, então nunca recorre à
identidade do `az login` de um desenvolvedor.
Os dois modos com token precisam de uma das roles de **dados de arquivo privilegiados**: conceda
`Storage File Data Privileged Contributor`, com escopo no compartilhamento. Uma role somente leitura
**não** basta nem para uma pasta de entrada, já que o pipeline obtém um lease do arquivo de entrada
enquanto o copia para processamento e o apaga após a verificação. Veja
[Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security#credenciais-de-armazenamento-do-azure-files).
**Um compartilhamento de trabalho, não vários.** `processing/`, `output/` e `error/` precisam ficar
juntos, porque promover um artefato verificado e mover a cópia preparada de um job que falhou são
operações de **renomeação**, e a renomeação do Azure não funciona entre compartilhamentos nem entre
contas de armazenamento. As pastas de entrada continuam sendo várias e independentes — cada uma pode
indicar a própria conta e o próprio compartilhamento —, porque copiar para processamento a partir de uma delas é
uma *cópia*.
**O que a inicialização recusa e o que ela apenas informa.** Um provider ou modo de credencial não
reconhecido, um bloco de credencial parcial, um compartilhamento NFS, um caminho `azurefiles://` em
`Storage:Root`, em `Logging:File:Path` ou — enquanto `Database:Provider` for `Sqlite` — em
`ConnectionStrings:Default`, e uma pasta de entrada que colida com uma das raízes de trabalho no
compartilhamento de trabalho: tudo isso **impede o host de iniciar**. Um compartilhamento
**inacessível**, não: ele é informado no console de operação, na página **Sistema** e pelo
`/api/ready`, e o host sobe — um compartilhamento fora do ar às 3h não pode transformar uma
reinicialização em um serviço que não inicia.
> **Observação: Um quarto item aparece no compartilhamento de trabalho, e não é uma pasta**
>
> O `bulksigner-instance.json` fica ao lado de `processing/`, `output/` e `error/`. Ele registra o nome
> do host e o ID do processo da instância que assumiu o compartilhamento, e é mantido sob um lease sem
> expiração durante toda a vida dessa instância. Não mexa nele: é ele que avisa, no próximo boot, que uma
> segunda instância está assinando a partir deste compartilhamento. Veja
> [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quando-outra-instância-parece-ser-dona-do-compartilhamento-de-trabalho).
Os downloads sempre passam em streaming pela aplicação — nenhuma URL de shared access signature é
criada para um artefato assinado, então o `GET /api/jobs/{id}/output` se comporta da mesma forma,
qualquer que seja o provider que guarda `output/`. Deliberadamente, não há ajuste de repetição: a
política do SDK está definida no código (três tentativas, backoff exponencial de 500 ms a 5 s, timeout
de rede de 30 segundos), e este produto já faz novas tentativas acima dela.
#### Overrides por pasta
Cada pasta de entrada monitorada escolhe o próprio provider e herda o resto, de modo que ler o
compartilhamento de um cliente na conta *dele*, enquanto o compartilhamento de trabalho fica na sua, é
um override por pasta, e não uma segunda implantação.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Storage:Inputs[N].Provider` | enum? | herda `Storage:Provider` | `Storage__Inputs__N__Provider` | `LocalFileSystem` ou `AzureFiles`, por pasta. Uma pasta pode ler um compartilhamento enquanto outra continua em disco local durante uma migração. |
| `Storage:Inputs[N].Path` | string | n/a | `Storage__Inputs__N__Path` | **REQUIRED.** Um caminho de sistema de arquivos em uma pasta local; em uma pasta `AzureFiles`, o **diretório dentro do compartilhamento**, separado por `/`. Uma barra invertida é recusada no boot — é um separador local sem significado em um compartilhamento. |
| `Storage:Inputs[N].AzureFiles:*` | objeto | herda `Storage:AzureFiles` campo a campo | `Storage__Inputs__N__AzureFiles__AccountName`, … | Os mesmos membros do bloco acima, exceto `Directory`. A herança verifica **nulo, não vazio**: uma chave omitida é herdada, e uma string vazia significa que esta pasta não tem esse valor. O `Directory` é **recusado** aqui. |
| `Storage:Inputs[N].PollIntervalSeconds` | int? | herda `WatchedFolder:PollIntervalSeconds` | `Storage__Inputs__N__PollIntervalSeconds` | **Na prática, REQUIRED em uma pasta `AzureFiles`** — uma pasta que não resolva para nenhum intervalo é recusada no boot. Em uma pasta **local**, *a presença da chave é a ativação*: definir um intervalo acrescenta enumeração periódica ao comportamento do observador dessa pasta, que é a solução para uma pasta montada a partir de um compartilhamento de rede. Faixa válida: 5–3600. |
> **Info: Se uma pasta faz polling e com que frequência são duas questões separadas**
>
> É a presença de `Storage:Inputs[N].PollIntervalSeconds` que ativa o polling em uma pasta *local*; o
> `WatchedFolder:PollIntervalSeconds` global (padrão `30`) é consultado apenas para a **frequência**.
> Uma implantação local existente que não define intervalo por pasta mantém inalterado o comportamento
> orientado a eventos.
#### Exemplo: o compartilhamento de trabalho no Azure Files, entradas ainda locais
```json
"Storage": {
"Root": "/var/lib/bulksigner",
"Provider": "AzureFiles",
"AzureFiles": {
"AccountName": "contosofiles",
"ShareName": "bulksigner",
"Directory": "prod",
"Credential": "ManagedIdentity"
},
"Inputs": [
{ "Name": "default", "Path": "/var/lib/bulksigner/input", "Provider": "LocalFileSystem" }
]
}
```
Os artefatos assinados vão para `bulksigner/prod/output` na conta `contosofiles`; `logs/` e `db/`
ficam em `/var/lib/bulksigner`. Se você remover a linha `Provider` da pasta de entrada, ela herda
`AzureFiles`; nesse caso, o `Path` dela passa a ser um diretório dentro do compartilhamento, e ela é
enumerada a cada `WatchedFolder:PollIntervalSeconds` — o Azure Files não publica notificações de
mudança, então uma pasta que não resolva para nenhum intervalo é recusada no boot.
#### Exemplo: pastas de entrada também em um compartilhamento, autenticando com chave de conta
Para um host que não tem nenhum acesso ao tenant — um servidor *on-premises* sem identidade gerenciada
e sem registro de aplicativo —, o `AccountKey` é o modo que resta:
```json
"Storage": {
"Root": "/var/lib/bulksigner",
"Provider": "AzureFiles",
"AzureFiles": {
"AccountName": "contosofiles",
"ShareName": "bulksigner",
"Credential": "AccountKey"
},
"Inputs": [
{ "Name": "remessas", "Provider": "AzureFiles", "Path": "entrada/remessas", "PollIntervalSeconds": 30 },
{ "Name": "contabil", "Path": "entrada/contabil", "AzureFiles": { "ShareName": "financeiro" }, "PollIntervalSeconds": 300 },
{ "Name": "legacy", "Provider": "LocalFileSystem", "Path": "/mnt/legacy/incoming" }
]
}
```
```bash
Storage__AzureFiles__AccountKey= # variável de ambiente recomendada; nunca versione
```
Cinco pontos que este exemplo mostra:
- **A chave é um segredo para todos os compartilhamentos que ela abre.** `contabil` sobrescreve
`ShareName` e nada mais, então `AccountName`, `Credential` e a chave são herdados campo a campo.
Escrever `"AccountKey": ""` em uma pasta significa que essa pasta *não* tem chave, e não um pedido
para herdar uma.
- **O `AccountKey` gera um aviso a cada boot**, no console de operação e no log durável, indicando cada
compartilhamento que ele abre.
- **Sem prefixo `Directory`, as raízes de trabalho ficam na raiz do compartilhamento.** As pastas de
entrada podem ficar no compartilhamento de trabalho, mas uma entrada cujo `Path` *seja* uma das três
raízes, fique dentro dela ou a contenha é recusada no boot — caso contrário, essa colisão apagaria um
artefato assinado a cada iteração, enquanto informaria todos os jobs como `Completed`.
- **As duas pastas remotas definem o próprio intervalo, e a local deliberadamente não define nenhum.**
É por não definir intervalo que o `legacy` continua orientado a eventos.
- **O banner confirma**, exibindo `azure credential = AccountKey`,
`work share = contosofiles/bulksigner`, os providers por pasta e `azure shares = 2 reachable` — os
compartilhamentos são testados separadamente, então uma chave que abre um e não o outro fica visível
na inicialização.
### `Storage:Inputs[].IgnoredExtensions` / `Storage:Inputs[].IgnoredPrefixes` (por pasta)
Arrays opcionais. A lista efetiva de itens ignorados é a **união** da lista base global
`WatchedFolder:IgnoredExtensions` / `WatchedFolder:IgnoredPrefixes` com os acréscimos por pasta. As
listas por pasta *se somam* à lista base; elas não conseguem desfazer o filtro de algo que a lista
global já filtra. Exemplo: com a lista base padrão (`.tmp`, `.part`, `.crdownload`, `.swp`), uma pasta
que declara `IgnoredExtensions: [".bak"]` filtra `.bak` *e* `.tmp` etc.
### Exemplo: duas pastas, uma com uma regra extra de ignorados
```json
"Storage": {
"Root": "/var/lib/bulksigner",
"Inputs": [
{ "Name": "default", "Path": "/var/lib/bulksigner/input" },
{
"Name": "legal",
"Path": "/mnt/legal/incoming",
"IgnoredExtensions": [".bak"]
}
]
}
```
## `Pipeline`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Pipeline:PollIntervalSeconds` | int | `2` | `Pipeline__PollIntervalSeconds` | Com que frequência o worker consulta a fila quando está ocioso. Menor = captura mais rápida, mais leituras no SQLite. Faixa válida: 1–3600. |
| `Pipeline:MaxConcurrency` | int | `1` | `Pipeline__MaxConcurrency` | Limite máximo de jobs simultâneos em andamento. O padrão `1` é sequencial. Aumente para ganhar vazão em implantações com PFX. Faixa válida: 1–32. **PKCS#11 / WindowsStore: mantenha em 1, a menos que o token / CSP permita sessões simultâneas — veja [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates).** |
| `Pipeline:RejectAlreadyProcessedFileNames` | bool | `true` | `Pipeline__RejectAlreadyProcessedFileNames` | *Novo na 2.13.0, ligado por padrão.* Recusa um arquivo cujo **nome** já pertence a um job `Completed` ou ainda ativo — a comparação vale para o host inteiro (todas as pastas monitoradas, todos os perfis, todos os uploads) e não diferencia maiúsculas de minúsculas, porque o `output/` é uma única pasta. Vindo de uma pasta monitorada ou de uma nova varredura, o arquivo vira um job que já nasce `Failed` com `file.already-processed` e é movido, sem assinatura, para a pasta `error/` desse job; um upload é recusado com `409` e o mesmo código. Jobs `Failed` e `Canceled` não reservam nome, e uma nova tentativa não está sujeita à regra. Apagar o job que detém o nome (em `/jobs`) faz com que ele volte a ser aceito. **Desligue para um produtor que reutiliza o mesmo nome de arquivo fixo todos os dias.** |
## `WatchedFolder`
O detector de estabilidade impede a captura de um arquivo que ainda está sendo escrito — o observador
espera até que o tamanho e a data da última escrita permaneçam idênticos ao longo de
`StabilityRequiredSamples` verificações consecutivas.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `WatchedFolder:StabilityPollIntervalMs` | int | `500` | `WatchedFolder__StabilityPollIntervalMs` | Intervalo entre verificações de estabilidade. Faixa válida: 50–10000. |
| `WatchedFolder:StabilityRequiredSamples` | int | `3` | `WatchedFolder__StabilityRequiredSamples` | Amostras idênticas consecutivas necessárias antes do enfileiramento. Faixa válida: 1–100. |
| `WatchedFolder:StabilityConcurrency` | int | `8` | `WatchedFolder__StabilityConcurrency` | Quantos arquivos candidatos cada pasta estabiliza e enfileira simultaneamente. A verificação de estabilidade bloqueia por aproximadamente `StabilityRequiredSamples × StabilityPollIntervalMs` por arquivo, então processá-los um de cada vez limita a entrada a cerca de um arquivo por esse intervalo; sobrepor as esperas mantém o pipeline alimentado quando um lote grande de arquivos chega de uma vez. Faixa válida: 1–64. |
| `WatchedFolder:StabilityTimeoutSeconds` | int | `60` | `WatchedFolder__StabilityTimeoutSeconds` | Espera máxima antes de desistir de um arquivo que nunca se estabiliza. Faixa válida: 1–3600. |
| `WatchedFolder:PollIntervalSeconds` | int? | `30` | `WatchedFolder__PollIntervalSeconds` | **Com que frequência uma pasta com polling é enumerada — e não se ela faz polling.** Sobrescrito, por pasta, por `Storage:Inputs[N].PollIntervalSeconds`, e é a presença *dessa* chave que liga o polling em uma pasta local; definir apenas esta não muda nada em lugar nenhum. Uma pasta `AzureFiles` sempre faz polling e usa este valor, a menos que defina o próprio. Faixa válida: 5–3600. |
| `WatchedFolder:IgnoredExtensions` | array | `[".tmp", ".part", ".crdownload", ".swp"]` | n/a (use a configuração) | Extensões de arquivo que o observador ignora por completo. |
| `WatchedFolder:IgnoredPrefixes` | array | `[".", "~$"]` | n/a (use a configuração) | Prefixos de nome de arquivo que o observador ignora (dotfiles, arquivos de lock do Office). |
## `Upload`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Upload:Enabled` | bool | `true` | `Upload__Enabled` | *Novo na 2.10.0.* Se este host aceita arquivos por upload. `false` desliga o recurso dos dois lados ao mesmo tempo: `POST /api/files` responde `409` com `upload.disabled` — antes de o perfil ser resolvido, então uma requisição recusada não revela nada sobre quais perfis existem — e a página **Jobs** não mostra o botão **Enviar arquivos**. Pastas monitoradas, nova varredura e nova tentativa não são afetadas, então uma implantação alimentada apenas pelas pastas não perde nada. Lido uma vez no boot; religá-lo exige reinicialização. |
| `Upload:MaxBytes` | long | `104857600` (100 MiB) | `Upload__MaxBytes` | Limite rígido do corpo da requisição de upload — tanto em `POST /api/files` quanto na caixa de diálogo **Enviar arquivos** do dashboard. Aumente para PDFs digitalizados pesados. Mínimo: 1024. |
## `Dashboard`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Dashboard:PollIntervalSeconds` | int | `5` | `Dashboard__PollIntervalSeconds` | Intervalo de atualização, no servidor, das páginas ao vivo do dashboard. Faixa válida: 1–60. |
## `Branding` — o logotipo do cliente nas páginas de login e de aprovação
*Novo na 2.10.0.* A página de login e as páginas do aprovador (`/approve/{id}`, `/approvals` e a página
que exige o link) mostram a marca do produto. Se você indicar aqui um **logotipo do cliente**, essas
páginas passam a mostrá-lo também: acima de uma versão reduzida da marca do produto no cartão de login e
ao lado dela nos cabeçalhos do aprovador. Um logotipo por implantação, sobre fundo branco — todos esses
cartões são brancos, então uma marca que só funciona sobre fundo escuro precisa ser exportada de novo.
Nada mais muda: a barra do aplicativo, os títulos das páginas e o favicon continuam sendo os do produto.
O logotipo é lido **uma vez, na inicialização**, de um arquivo no host ou de um blob do Azure — os
mesmos dois lugares em que o material de um certificado pode estar, e pelo mesmo motivo: em um host do
Azure App Service, o operador não tem onde colocar um arquivo. Indique **um** dos dois. Não indicar
nenhum é válido e significa sem logotipo; indicar os dois é recusado no boot.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Branding:CustomerLogo:Path` | string | — | `Branding__CustomerLogo__Path` | Caminho para o arquivo de imagem no host — informe um caminho absoluto. A extensão define o content type e precisa ser `.png`, `.jpg`/`.jpeg`, `.webp` ou `.svg`; qualquer outra é recusada no boot. No Docker, use um bind mount somente leitura (o arquivo compose do pacote de implantação traz um exemplo comentado). Permitido com `Cluster:Enabled`, mas, nesse caso, o arquivo precisa ser idêntico em todas as instâncias; nada verifica isso. |
| `Branding:CustomerLogo:Blob:Url` | string | `""` | `Branding__CustomerLogo__Blob__Url` | URL `https://` completa do blob. Uma query string é recusada (sem SAS). A extensão do nome do blob segue a mesma regra da extensão do caminho. Mesmo formato do bloco [`…:Blob`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#blob--lendo-o-arquivo-do-azure-blob-storage) de um certificado; nada é herdado desse bloco nem de `Storage:AzureFiles`. |
| `Branding:CustomerLogo:Blob:Credential` | string | — | `Branding__CustomerLogo__Blob__Credential` | `ManagedIdentity`, `ServicePrincipal` ou `AccountKey`. Sem valor padrão. Uma credencial por token precisa da role **Storage Blob Data Reader** no container. |
| `Branding:CustomerLogo:Blob:TenantId` / `AppId` / `AppSecret` | string | — | `Branding__CustomerLogo__Blob__…` | Somente no modo `ServicePrincipal`; os três são obrigatórios. `AppSecret` é **SECRET** — defina-o pelo ambiente. |
| `Branding:CustomerLogo:Blob:AccountKey` | string | — | `Branding__CustomerLogo__Blob__AccountKey` | Somente no modo `AccountKey`. **SECRET**, e dá acesso à conta de armazenamento inteira — prefira uma credencial por token. |
**Recusado no boot, com a indicação da chave:** `Path` e `Blob` definidos ao mesmo tempo; uma extensão
fora das cinco; um bloco de blob sem `Url` ou sem `Credential`, um modo de credencial sem os valores
correspondentes ou uma URL com query string. Cada um desses casos é uma configuração que não poderia
funcionar, qualquer que fosse o conteúdo do arquivo.
**Informado e tolerado:** o arquivo ou blob está ausente, ilegível ou inacessível; o arquivo está vazio
ou passa de **256 KiB**; os bytes não correspondem à extensão. O serviço inicia, assina e atende; as
páginas mostram só a marca do produto; e o motivo aparece na linha `customer logo` do banner de
inicialização, como aviso no log de inicialização e como alerta na página **Sistema** até a próxima
reinicialização. O `/api/ready` não traz linha para ele — uma imagem não deve decidir se um serviço de
assinatura está pronto. Corrija o arquivo ou a configuração e reinicie.
**Como ele chega ao navegador.** `GET /branding/customer-logo` serve os bytes anonimamente, porque as
páginas que o exibem são mostradas antes do login. A URL contém um hash dos bytes e é armazenada em
cache como imutável, então uma reinicialização com um arquivo novo a altera em todos os lugares ao
mesmo tempo, e ninguém precisa limpar o cache. Um SVG é servido com uma política que impede a execução
de qualquer script que ele contenha. Sem logotipo carregado, a rota responde `404` com
`branding.customer-logo-not-available`.
```jsonc
{
"Branding": {
"CustomerLogo": {
// Um arquivo neste host. Absoluto; .png, .jpg, .jpeg, .webp ou .svg; no máximo 256 KiB.
"Path": "/etc/bulksigner/customer-logo.png"
// OU, para um host sem disco onde colocá-lo (App Service): um blob do Azure, lido uma vez no boot.
// Remova o Path acima se usar isto — os dois definidos é recusado.
// ,"Blob": {
// "Url": "https://contoso.blob.core.windows.net/branding/customer-logo.svg",
// "Credential": "ManagedIdentity"
// }
}
}
}
```
## `ApproverPortal`
Dá suporte à fila por aprovador em `/approvals`. **Desligado por padrão**, de modo que uma implantação
que já usa a [etapa de aprovação](https://docs.lacunasoftware.com/articles/bulk-signer/approvals) é atualizada sem mexer na configuração. Lido uma vez na
inicialização — ao mudar qualquer coisa, reinicie.
De propósito, é uma seção de nível superior em vez de um bloco por perfil: um link identifica uma
*pessoa*, e a mesma pessoa costuma participar dos pools de vários perfis.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `ApproverPortal:Enabled` | bool | `false` | `ApproverPortal__Enabled` | Chave geral. Quando falsa, nenhum link é resolvido, nenhuma sessão é emitida, e a única forma de aprovação é o link por job. |
| `ApproverPortal:LinkSecret` | string | — | `ApproverPortal__LinkSecret` | **REQUIRED quando habilitado, SECRET — nunca o versione.** O link de cada aprovador é `HMAC-SHA256(este, o e-mail dele)`, então qualquer pessoa que o leia pode aprovar arquivos de pagamento em nome de qualquer aprovador configurado. Mínimo de 32 caracteres, verificado na inicialização. Precisa ser durável: gerar um a cada boot invalidaria o favorito de todos os aprovadores a cada reinicialização. **Mudá-lo revoga o link de todos os aprovadores de uma vez** — a medida drástica prevista para o caso de "o segredo vazou". |
| `ApproverPortal:DecidedLookback` | TimeSpan | `90.00:00:00` | `ApproverPortal__DecidedLookback` | Até quando, no passado, a aba **Aprovados** do portal mostra itens. Limita o quanto vale um link roubado. A aba também é limitada a 200 linhas por carregamento e avisa quando o limite é atingido. |
| `ApproverPortal:SessionLifetime` | TimeSpan | `30.00:00:00` | `ApproverPortal__SessionLifetime` | Tempo de vida do cookie emitido na troca do link. Com expiração deslizante, para que um aprovador que está percorrendo uma fila não seja desconectado no meio de uma decisão. |
| `ApproverPortal:PollInterval` | TimeSpan | `00:00:10` | `ApproverPortal__PollInterval` | Com que frequência um portal aberto relê a fila, para que a decisão de um colega, uma liberação ou um prazo de espera vencido apareçam sem que o aprovador precise clicar em nada. É uma chave própria, e não uma fração do [`Dashboard:PollIntervalSeconds`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#dashboard), porque esta se multiplica por todos os aprovadores com uma aba aberta, enquanto aquela é ajustada por poucos operadores. Faixa válida: `00:00:01` a `00:05:00`, verificada somente quando `Enabled` é verdadeiro. **Não há valor que desligue o polling** — um intervalo longo é a forma de reduzir a carga, e a página tem, de qualquer forma, um botão de atualização manual. Cuidado: um `10` sem formato é lido como dez *dias* e é recusado, com a indicação do valor recebido. |
`Enabled = true` sem **nenhum** perfil de assinatura armazenado com regra de aprovação gera um **aviso
de inicialização**, não uma recusa — um portal sem nenhum pool mostra uma fila vazia a todos os
aprovadores e parece quebrado, então o banner e o log durável avisam isso.
> **Aviso: Mudou na 2.1.0 — um aviso, não mais uma recusa**
>
> Até a 2.0.x, isso impedia o boot. Com os perfis no banco operacional e o `Signing:Profiles[]` servindo
> só como seed, a recusa dispararia exatamente no estado a que esta página recomenda chegar: pools no
> banco e a seção apagada.
```json
{
"ApproverPortal": {
"Enabled": true,
"LinkSecret": "substitua-por-32+-caracteres-aleatorios-mantidos-secretos",
"DecidedLookback": "90.00:00:00",
"SessionLifetime": "30.00:00:00"
}
}
```
O link de cada aprovador é mostrado na página **Sistema** do dashboard, um por pessoa configurada.
Envie a cada aprovador apenas o link dele; trate-o como a senha dessa pessoa. Veja
[Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals#o-portal-do-aprovador).
## `Console:Dashboard`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Console:Dashboard:Enabled` | bool | `true` | `Console__Dashboard__Enabled` | Se o dashboard ao vivo no terminal pode substituir a narração de log por job na saída padrão. Ele só é ativado quando esta chave é verdadeira **e** o processo é um console em primeiro plano (não serviço do Windows / systemd / Docker) **e** a saída padrão é um terminal interativo. Por isso, instalações como serviço e em container não são afetadas por esta chave. Defina-a como `false` quando rodar o binário em primeiro plano e quiser logs simples em fluxo contínuo. |
## Idioma de exibição — deliberadamente não configurável
Não há seção de configuração para o idioma da interface, e isso é uma decisão, não uma lacuna. As
páginas web são exibidas em `en-US` ou `pt-BR` conforme uma **preferência de apresentação por
navegador**: o seletor de idioma faz um POST no endpoint anônimo `POST /api/culture`, que grava por um
ano o cookie de cultura padrão do ASP.NET Core. A ordem de resolução é **cookie → o `Accept-Language`
do navegador → `en-US`**, então um operador brasileiro recebe português já no primeiro carregamento,
sem que ninguém configure nada, e não há configuração no servidor que se sobreponha ao que cada leitor
escolheu.
O que permanece sempre em inglês, independentemente da escolha do leitor: as mensagens de auditoria
persistidas (elas são evidência), a saída de log, o dashboard de console, o texto dos problemas
retornados pela API REST, os valores de `JobStatus` no protocolo e todo o vocabulário e formatação do
CNAB240. Veja [Dashboard](https://docs.lacunasoftware.com/articles/bulk-signer/dashboard#idioma-de-exibição).
## `LogViewer`
Dá suporte ao armazenamento em memória das exceções recentes e à página `/logs` do dashboard. Todos os
valores são lidos uma vez na inicialização — ao mudá-los, reinicie.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `LogViewer:Enabled` | bool | `true` | `LogViewer__Enabled` | Chave geral. Quando falsa, o destino em memória não é conectado, a página `/logs` mostra um aviso de recurso desabilitado, e o link de navegação fica oculto. |
| `LogViewer:MaxEntries` | int | `20` | `LogViewer__MaxEntries` | Tamanho do buffer limitado em memória e número máximo de entradas exibidas. As entradas mais antigas são descartadas quando esse limite é ultrapassado. Faixa válida: 1–1000. |
| `LogViewer:RefreshIntervalSeconds` | int | `5` | `LogViewer__RefreshIntervalSeconds` | Intervalo de atualização automática da página `/logs`. Faixa válida: 1–60. A página também tem um botão de atualização manual. |
| `LogViewer:Levels` | string[] | `["Error","Fatal"]` | `LogViewer__Levels__0`, … | Níveis de log que o armazenamento captura (sem diferenciar maiúsculas de minúsculas). Nomes válidos: `Verbose`, `Debug`, `Information`, `Warning`, `Error`, `Fatal`. Nomes vazios ou desconhecidos impedem a inicialização. **O `Logging:File:MinimumLevel` continua sendo aplicado antes** — ampliar esta lista para níveis abaixo desse mínimo não captura nada, porque esses eventos nunca chegam ao destino. |
Veja [Dashboard](https://docs.lacunasoftware.com/articles/bulk-signer/dashboard#logs--exceções-recentes).
## `Readiness`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Readiness:RequireApiKey` | bool | `false` | `Readiness__RequireApiKey` | *Novo na 2.6.0.* Quando verdadeiro, `GET /api/ready` exige autenticação por chave de API ou cookie — a mesma proteção que o `Metrics:RequireApiKey` aplica a `/api/metrics`. Ao contrário daquela chave, fica desligado por padrão, porque o principal consumidor do probe é um health check de plataforma que não consegue enviar a chave: o do Azure App Service precisa ser respondido anonimamente, e um `401` em todas as instâncias torna todas elas não saudáveis de uma vez. Ligue-o onde quem executa o probe consiga enviar `X-API-Key` (os headers de um probe do Kubernetes, um agente de monitoramento) ou onde não haja probe. Lido uma vez na inicialização; a linha `ready` do banner mostra `(anonymous)` ou `(API key)`. O `/api/ready/details` exige autenticação de qualquer forma, e o `/api/ready` nunca traz detalhes. |
> **Aviso: Mudou na 2.6.0 — o probe anônimo responde com um veredito, não com uma descrição**
>
> O `/api/ready` agora retorna `ready` e o `name` e o `ok` de cada verificação — o campo `detail` por
> verificação não é mais retornado. Esse detalhe indicava o host do SQL Server, todos os compartilhamentos
> de entrada e a localização de um certificado degradado: nada disso é credencial, mas, em conjunto,
> formava um mapa da implantação legível por qualquer pessoa que chegasse à porta. O mesmo relatório, com
> todos os detalhes, está em **`/api/ready/details`**, protegido pela chave de API ou por uma sessão de
> operador, com a mesma regra `200` / `503`. Um orquestrador que lê o código de status não é afetado; um
> monitoramento que interpretava o `detail` passa a usar a rota de detalhes e acrescenta o header. Cada
> mudança de veredito de uma verificação é gravada uma vez no log durável, então o histórico continua
> registrado.
## `Metrics`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Metrics:RequireApiKey` | bool | `true` | `Metrics__RequireApiKey` | Quando verdadeiro, `/api/metrics` exige autenticação por chave de API ou cookie. Defina como falso apenas se o coletor do Prometheus estiver dentro do perímetro de confiança e a rede estiver fechada. |
Veja [API REST](https://docs.lacunasoftware.com/articles/bulk-signer/rest-api) para o inventário completo das métricas.
## `Statistics`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Statistics:Enabled` | bool | `true` | `Statistics__Enabled` | Chave geral. Quando falsa, o coletor não faz nada (sem registro, sem locking), nenhuma linha é gravada, e o painel do dashboard fica oculto. As estatísticas são uma linha por job concluído no banco operacional: elas **sobrevivem a reinicializações**, e todas as instâncias de um cluster leem os mesmos números. Desligar a chave não apaga as linhas já registradas — religá-la volta a mostrá-las; para limpar o painel, use o [Limpar Jobs](https://docs.lacunasoftware.com/articles/bulk-signer/operations#limpar-jobs). |
Veja [Estatísticas de jobs](https://docs.lacunasoftware.com/articles/bulk-signer/statistics) para o significado de cada número.
## `Backup`
Dá suporte ao recurso de backup do banco de dados: a página `/backup` do dashboard, o
`GET|POST /api/backup` e o agendador. **Desligado por padrão, e somente para SQLite** — com
`Database:Provider = SqlServer`, `Backup:Enabled = true` causa uma **recusa de boot**, em vez de
simplesmente não fazer nada, porque o backup do banco operacional é responsabilidade da rotina desse
SGBD. Como o modo cluster exige `SqlServer`, essa combinação é, por definição, impossível nele.
Lido uma vez na inicialização. Somente o bloco do destino selecionado é lido.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Backup:Enabled` | bool | `false` | `Backup__Enabled` | Chave geral. Quando falsa, nada é agendado, o botão de iniciar e o `POST /api/backup` recusam com `backup.disabled`, e a página explica o que ligar. **`true` com `Database:Provider = SqlServer` impede o boot**, indicando as duas chaves e a solução. |
| `Backup:Destination` | string | `Disk` | `Backup__Destination` | `Disk`, `S3` ou `AzureBlob`. Se ausente, vale `Disk` — o destino que não precisa de credencial. Não diferencia maiúsculas de minúsculas; um valor não reconhecido é recusado no boot, com a indicação da chave, do valor informado e dos nomes válidos. |
| `Backup:IntervalHours` | int? | *(ausente)* | `Backup__IntervalHours` | Com que frequência um backup roda automaticamente. **Ausente significa somente manual** — o recurso está ligado, o botão funciona, mas nada roda por temporizador. Faixa válida: 1–8760. Deliberadamente, é um intervalo e não um horário do dia: um horário precisa de fuso horário, e este produto não tem um. A contagem parte da última execução **bem-sucedida**, de modo que uma reinicialização não a zera e uma execução com falha não a consome. Se nunca houve backup, um backup é devido imediatamente. |
| `Backup:RetainCount` | int | `14` | `Backup__RetainCount` | Quantos artefatos manter no destino. `0` mantém **todos** — e nem chega a listar o destino —, para um bucket ou container cujas próprias regras de ciclo de vida fazem a limpeza. Faixa válida: 0–1000. É uma contagem e não uma idade, porque uma contagem continua válida quando alguém muda o `IntervalHours`. A limpeza roda apenas após um armazenamento bem-sucedido e **não pode fazer a execução falhar**. |
| `Backup:Disk:Path` | string | — | `Backup__Disk__Path` | **REQUIRED quando `Destination = Disk`.** Sem padrão, de propósito: qualquer padrão plausível colocaria o backup no mesmo disco do banco de dados copiado. Um caminho UNC ou volume de rede montado serve. Quatro recusas de boot: ausente; o esquema `azurefiles://` do próprio produto (deliberadamente, um destino de backup **não** é um provider de armazenamento); um caminho dentro de uma **pasta de entrada monitorada** (o pipeline capturaria, assinaria e depois *apagaria* o backup); e um caminho dentro de `processing/`, `output/`, `error/` ou `db/`. |
| `Backup:S3:BucketName` | string | — | `Backup__S3__BucketName` | **REQUIRED quando `Destination = S3`.** |
| `Backup:S3:Region` | string | — | `Backup__S3__Region` | **REQUIRED quando `Destination = S3` e nenhum `ServiceUrl` está definido.** O nome de sistema da região, por exemplo `sa-east-1`. Sem valor padrão: o SDK recorreria ao ambiente do host ou ao perfil compartilhado, então uma região omitida decidiria, por acidente, onde fica armazenada uma cópia do registro de aprovação de pagamentos. |
| `Backup:S3:Prefix` | string | `""` | `Backup__S3__Prefix` | Prefixo de chave dentro do bucket. Normalizado — `bulksigner`, `bulksigner/` e `/bulksigner/` significam a mesma coisa. |
| `Backup:S3:Credential` | string | — | `Backup__S3__Credential` | **REQUIRED quando `Destination = S3`.** `AccessKey` ou `InstanceRole`. **Sem valor padrão** — a própria cadeia do SDK da AWS autenticaria com qualquer identidade que o host tivesse. Uma URL pré-assinada não é uma credencial aceita. |
| `Backup:S3:AccessKeyId` | string | — | `Backup__S3__AccessKeyId` | **REQUIRED quando `Credential = AccessKey`.** Deliberadamente **não** mascarado nos logs: é a metade identificadora do par, e mascará-lo removeria o único valor que diz qual chave uma requisição recusada usou. Defini-lo com `InstanceRole` causa uma recusa de boot. |
| `Backup:S3:SecretAccessKey` | string | — | `Backup__S3__SecretAccessKey` | **REQUIRED quando `Credential = AccessKey`. SECRET.** Defini-lo com `InstanceRole` causa uma recusa de boot. |
| `Backup:S3:ServiceUrl` | string | — | `Backup__S3__ServiceUrl` | Um endpoint de **API** compatível com S3 em vez da AWS — MinIO, Ceph, Wasabi, Backblaze B2. URL `http://` ou `https://` absoluta. Quando definido, `Region` passa a ser opcional. |
| `Backup:S3:ForcePathStyle` | bool | `false` | `Backup__S3__ForcePathStyle` | Endereça os buckets como um segmento de caminho (`host/bucket/key`) em vez de subdomínio. `false` é o que a própria AWS espera; **quase todo endpoint compatível com S3 precisa de `true`** — deixá-lo falso com MinIO ou Ceph causa uma falha de DNS que cita um hostname que você nunca configurou. |
| `Backup:AzureBlob:ContainerUrl` | string | — | `Backup__AzureBlob__ContainerUrl` | **REQUIRED quando `Destination = AzureBlob`.** A URL https completa do container, por exemplo `https://contoso.blob.core.windows.net/bulksigner-backups`. Recusada no boot quando não é uma URL https absoluta, não indica uma conta, tem mais de um segmento de caminho (coloque um caminho em `Prefix`) ou **tem query string** — que é como uma SAS chega; recusá-la garante que este valor nunca seja secreto. O container **não** é criado automaticamente. |
| `Backup:AzureBlob:Prefix` | string | `""` | `Backup__AzureBlob__Prefix` | Prefixo de nome de blob dentro do container. Normalizado como o do S3. |
| `Backup:AzureBlob:Credential` | string | — | `Backup__AzureBlob__Credential` | **REQUIRED quando `Destination = AzureBlob`.** `ManagedIdentity`, `ServicePrincipal` ou `AccountKey` — grafados exatamente como os de `Storage:AzureFiles`. **Sem valor padrão.** O `ManagedIdentity` aceita **somente a identidade atribuída pelo sistema**. A identidade precisa da role **`Storage Blob Data Contributor`** — escrita, e não o `Storage Blob Data Reader` de que um blob de certificado precisa. |
| `Backup:AzureBlob:TenantId` / `AppId` | string | — | `Backup__AzureBlob__TenantId`, … | **REQUIRED quando `Credential = ServicePrincipal`.** |
| `Backup:AzureBlob:AppSecret` | string | — | `Backup__AzureBlob__AppSecret` | **REQUIRED quando `Credential = ServicePrincipal`. SECRET.** Defini-lo com outro modo causa uma recusa de boot — um segredo que esta implantação não usa é um segredo que ninguém vai rotacionar. |
| `Backup:AzureBlob:AccountKey` | string | — | `Backup__AzureBlob__AccountKey` | **REQUIRED quando `Credential = AccountKey`. SECRET.** Concede acesso total ao plano de dados da conta de armazenamento **inteira** e não pode ser restrita a um container. Defini-la com outro modo causa uma recusa de boot. |
Nada é herdado de `Storage:AzureFiles` nem do bloco de um blob de material de assinatura, mesmo quando
o mesmo aplicativo do Entra é indicado: essas credenciais concedem acesso de leitura a recursos
diferentes, e esta precisa de acesso de **escrita**.
### Exemplo: um intervalo para um volume local
```jsonc
{
"Backup": {
"Enabled": true,
"Destination": "Disk",
"IntervalHours": 24,
"RetainCount": 14,
"Disk": { "Path": "/backup/bulksigner" }
}
}
```
### Exemplo: um endpoint compatível com S3 (MinIO)
```jsonc
{
"Backup": {
"Enabled": true,
"Destination": "S3",
"IntervalHours": 12,
"RetainCount": 0, // as regras de ciclo de vida do bucket fazem a poda
"S3": {
"BucketName": "bulksigner-backups",
"ServiceUrl": "https://minio.internal:9000",
"ForcePathStyle": true,
"Credential": "AccessKey",
"AccessKeyId": "…",
// Na prática, defina via Backup__S3__SecretAccessKey, não aqui.
"SecretAccessKey": ""
}
}
}
```
Veja [Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention#disciplina-de-backup) para entender como isso se encaixa no quadro mais
amplo de retenção.
## `Cluster` — implantação com múltiplas instâncias
Modo cluster — mais de uma instância ativa cooperando sobre um único banco operacional e um único
compartilhamento de trabalho. **Desligado por padrão, e, desligado, é byte a byte o produto de
instância única**: uma implantação que nunca escreve esta seção não precisa de nada e não muda nada na
atualização.
Estas três chaves são a menor parte da ativação do modo. O custo dele está descrito em
[Alta disponibilidade e seus limites](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability) — leia essa página antes de definir
`Enabled = true` —, e a implantação é descrita em [Azure App Service (modo cluster)](https://docs.lacunasoftware.com/articles/bulk-signer/azure), que
também traz as configurações de plataforma (afinidade de sessão, o caminho do health check, Always On)
que não têm chave aqui porque não cabe a este produto defini-las.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Cluster:Enabled` | bool | `false` | `Cluster__Enabled` | Chave geral. Quando verdadeira, as recusas de boot abaixo se aplicam; quando falsa ou ausente, nenhum mecanismo de cluster é registrado e nada muda. Há exatamente uma topologia com múltiplas instâncias suportada: um Azure Web App (container Linux) com escala horizontal em um único App Service Plan. |
| `Cluster:HeartbeatSeconds` | int | `15` | `Cluster__HeartbeatSeconds` | Com que frequência esta instância grava uma linha informando que está ativa. Faixa `[5, 300]`; valores fora dela são recusados no boot. Lida somente quando o modo está ligado. |
| `Cluster:StaleAfterSeconds` | int | `60` | `Cluster__StaleAfterSeconds` | Quanto tempo uma instância pode ficar sem enviar heartbeats antes que as demais a presumam morta. Faixa `[15, 3600]`, e recusada no boot quando vale **menos de três intervalos de heartbeat** — um limiar tão curto presume a morte após um ou dois heartbeats perdidos, e um heartbeat pode se perder por motivos que não são morte (uma pausa de coleta de lixo, um banco operacional que demorou um instante, um container que a plataforma deixou sem recursos por um momento). O padrão são quatro intervalos. |
### O que `Enabled = true` recusa no boot
Toda configuração de cluster que não poderia funcionar causa uma recusa que indica as chaves e a
solução:
- **`Database:Provider` precisa ser `SqlServer`.** O banco operacional é o ponto de coordenação do
cluster, e um arquivo SQLite não pode ser compartilhado entre hosts.
- **O compartilhamento de trabalho e todas as pastas de entrada monitoradas precisam estar em
`AzureFiles`.** O lease do armazenamento local de arquivos não exclui nada fora do próprio processo,
e uma pasta local de uma instância é invisível para as demais. A pasta `default`, criada
automaticamente sem configuração, é local, então uma primeira execução com a chave ligada também é
recusada.
- **As origens de certificado por host `Pkcs11` e `WindowsStore` são recusadas.** Um token ou um
repositório de máquina fica em uma única máquina, e as instâncias de cluster são intercambiáveis. Use
`Pfx` (de preferência [lido de um blob](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#blob--lendo-o-arquivo-do-azure-blob-storage)) ou
`AzureKeyVault`. Um bloco `Certificate` obsoleto em um perfil com `Method = LacunaSigner` continua
tolerado, como em qualquer outro caso. É uma regra sobre um **perfil**, então é aplicada sempre que
uma origem de certificado é declarada: no boot, para uma entrada de `Signing:Profiles[]` ou para o
bloco legado `Signing:Certificate`, e no formulário do dashboard quando um perfil é criado ou o
certificado dele é alterado. Um perfil **salvo antes de a chave ser ligada** gera só um **aviso** de
inicialização com o nome do perfil e a origem — um perfil armazenado nunca é revalidado, e a recusa
tiraria do ar justamente a página que resolve o problema. Esse perfil assina na instância em que
encontrou o hardware e falha como perfil degradado em todas as outras.
Uma recusa **não** tem nada a ver com configuração, e está listada aqui porque parece ter: com o modo
ligado, o próprio marcador do compartilhamento de trabalho registra a qual banco operacional o
compartilhamento pertence, e uma instância cujo banco não corresponde se recusa a iniciar, indicando os
dois. É uma característica do compartilhamento, e não do `appsettings.json`, e é a única coisa que
detecta dois clusters apontados para o mesmo compartilhamento de trabalho.
Deliberadamente, uma condição de cluster gera um **aviso, não uma recusa**: modo cluster com
`Logging:AzureTable:Enabled = false` registra um Critical na inicialização, porque, na topologia
suportada, o disco da instância é efêmero e os arquivos de log rotacionados são descartados a cada
reciclagem. De qualquer forma, a regra de que a tabela nunca pode ser o único destino mantém o destino
de arquivo local ligado.
### Identidade e vivacidade
**A identidade é derivada, nunca configurada, e deliberadamente não há chave para ela.** Uma instância
se identifica pelo ID de instância da própria plataforma (`WEBSITE_INSTANCE_ID`, que o App Service
define em toda instância) ou, onde essa variável não existe, pelo nome da máquina, mais um
identificador de **encarnação** novo a cada boot. O App Service cria e destrói instâncias por regra de
escala, então um nome digitado por um operador seria uma chave que ninguém conseguiria manter correta;
é a encarnação que permite a uma instância distinguir *a própria vida anterior* de uma instância
estranha.
Cada instância mantém uma linha no banco operacional — identidade, encarnação, versão da aplicação,
quando iniciou, quando informou pela última vez que estava ativa —, atualizada a cada
`Cluster:HeartbeatSeconds` e presumida morta depois de `Cluster:StaleAfterSeconds`. A
[página Sistema](https://docs.lacunasoftware.com/articles/bulk-signer/dashboard#system--sistema) exibe essa tabela na visão **Instâncias**. Duas coisas
dependem dela no boot, deliberadamente de naturezas diferentes — uma assume a identidade, a outra só
avisa:
- **Uma instância que, ao subir, encontra a própria identidade já enviando heartbeats a desloca**,
indicando a encarnação deslocada no console e no log, e continua. É a sobreposição típica de uma
reimplantação: o App Service roda o container antigo e o novo com o mesmo ID de instância e mantém o
antigo ativo até o novo estar aquecido. O processo deslocado **se retira** no próximo heartbeat — não
captura nenhum job novo, termina os que já tem e mostra uma linha `cluster-instance` vermelha no
próprio `/api/ready`, que não reprova o probe. O dono de um job é a **encarnação**, além da
identidade, então em qualquer momento exatamente um processo captura trabalho com um determinado
nome, e o que o processo deslocado deixar inacabado é assumido `Cluster:StaleAfterSeconds` depois do
deslocamento. Um desligamento limpo desativa a linha de heartbeat, então um sucessor não tem nada a
deslocar. Dois hosts que de fato apresentam o mesmo nome se deslocam mutuamente, com avisos dos dois
lados, em vez de serem recusados; a única recusa que resta é a de um registro que perdeu todas as
disputas de escrita pela própria linha.
- **Instâncias ativas em uma versão diferente da aplicação registram um Critical, e o boot continua.**
Na topologia suportada, as atualizações exigem parada total, então isso indica um deployment slot
trocado para dentro de um cluster em execução, ou uma implantação que não parou todas as instâncias.
De propósito, é um aviso, e não uma recusa: a recusa impediria as instâncias de subir por todo o
tempo que um heartbeat *morto* da versão antiga levasse para ficar obsoleto, que é exatamente quando
um operador precisa que elas subam.
Se o banco operacional estiver inacessível no boot, o registro não é feito, assim como a migração, e o
host inicia mesmo assim. O heartbeat faz o registro no primeiro envio que conseguir chegar ao banco —
deslocando um detentor ativo da identidade exatamente como o boot teria feito —, então a instância só
fica ausente da visão **Instâncias** até esse momento.
> **Aviso: Mudou na 2.5.0 — deslocada, não mais recusada**
>
> Até a 2.4.x, uma instância que, ao subir, encontrasse a própria identidade enviando heartbeats se
> recusava a iniciar (a 2.4.3 passou a fazê-la esperar), então uma troca de imagem in-place, no App
> Service, custava pelo menos um início recusado. As atualizações continuam exigindo parada total, e
> *parar, implantar, iniciar* continua sendo a receita mais limpa; veja
> [Azure App Service](https://docs.lacunasoftware.com/articles/bulk-signer/azure#8-atualizações-exigem-parada-total).
### O key ring de sessão não tem chave própria, e não precisa de nenhuma
Com a chave desligada, o ring de Data Protection fica em `keys/`, dentro de `Storage:Root`,
criptografado com DPAPI no Windows. Com ela ligada, ele passa a ser um conjunto de linhas no banco
operacional, em texto claro, protegido pelo controle de acesso do próprio banco de dados — porque um
ring derivado de uma raiz local pertence estruturalmente a um único host; assim, atrás de um
balanceador de carga, um cookie criado por uma instância é rejeitado pela seguinte, o que as pessoas
percebem como uma desconexão intermitente, sem que nada, em lugar nenhum, a informe. Os dois cookies
dependem dele, então o problema afeta operadores e aprovadores da mesma forma.
Deliberadamente, ele acompanha a chave, e não uma configuração própria: uma implantação que pode
escolher o local do ring independentemente da topologia é uma implantação que pode escolher a
combinação que não funciona. Veja [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) e
[Alta disponibilidade](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability#o-key-ring-de-sessão-fica-em-texto-claro-no-banco).
## `Telemetry`
Exportação opcional para o Azure Application Insights. Desligada por padrão; quando desligada, o
serviço não depende do Application Insights e não faz chamadas de saída para ele.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Telemetry:Enabled` | bool | `false` | `Telemetry__Enabled` | Chave geral. Quando `true`, uma connection string é **obrigatória** — a inicialização falha sem ela. |
| `Telemetry:ConnectionString` | string? | `null` | `Telemetry__ConnectionString` | **SECRET.** Connection string do Application Insights. Deixe-a sem definir para fornecê-la pela variável de ambiente padrão `APPLICATIONINSIGHTS_CONNECTION_STRING`. |
| `Telemetry:RoleName` | string | `Lacuna.BulkSigner` | `Telemetry__RoleName` | Informado como a dimensão `cloud_RoleName`, para que vários serviços que compartilham um recurso continuem distinguíveis. |
Veja [Telemetria](https://docs.lacunasoftware.com/articles/bulk-signer/telemetry) para saber o que é coletado e as consultas KQL para analisar esses
dados.
## `RateLimiting`
Políticas de limite de requisições (rate limiting) com janela fixa, por IP.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `RateLimiting:Enabled` | bool | `true` | `RateLimiting__Enabled` | Chave geral. Desabilite em instalações em rede fechada. |
| `RateLimiting:Upload:PermitsPerWindow` | int | `30` | `RateLimiting__Upload__PermitsPerWindow` | Requisições permitidas por janela em `POST /api/files`. |
| `RateLimiting:Upload:WindowSeconds` | int | `60` | `RateLimiting__Upload__WindowSeconds` | Duração da janela da política de upload. |
| `RateLimiting:Upload:QueueLimit` | int | `0` | `RateLimiting__Upload__QueueLimit` | Quantas requisições acima do limite esperam, em vez de serem rejeitadas imediatamente. 0 = rejeitar imediatamente. |
| `RateLimiting:Actions:PermitsPerWindow` | int | `60` | `RateLimiting__Actions__PermitsPerWindow` | Requisições permitidas por janela nos endpoints de ação (nova tentativa, cancelar, nova varredura, limpeza, pausar, retomar). |
| `RateLimiting:Actions:WindowSeconds` | int | `60` | `RateLimiting__Actions__WindowSeconds` | Duração da janela da política de ações. |
| `RateLimiting:Actions:QueueLimit` | int | `0` | `RateLimiting__Actions__QueueLimit` | Tamanho da fila da política de ações. |
| `RateLimiting:Approval:*` | mesmo formato | `10` por `60` s | `RateLimiting__Approval__PermitsPerWindow`, … | Cota da rota anônima `POST /api/approvals/{id}`, separada da cota das ações de operador. Os IDs de job são GUIDs v4, e é esta política que os torna impossíveis de adivinhar por uma máquina, e não apenas por uma pessoa. |
| `RateLimiting:Export:*` | mesmo formato | `10` por `60` s | `RateLimiting__Export__PermitsPerWindow`, … | Cota das exportações para Excel: a exportação da fila do portal do aprovador e, desde a 2.11.0, o `GET /api/jobs/export` da página **Jobs**. Limita a velocidade com que cópias de uma fila podem ser feitas, e fica separada da cota `Approval` para que uma rajada de exportações nunca consuma as permissões de que um colega precisa para registrar uma decisão. |
| `RateLimiting:SignerNotification:*` | mesmo formato | `600` por `60` s | `RateLimiting__SignerNotification__PermitsPerWindow`, … | *Novo na 2.16.0.* Cota do `POST /api/signer/notifications`, o webhook do Lacuna Signer. Generosa porque o único chamador legítimo entrega todos os eventos da organização a partir de poucos endereços, então a partição por IP é, na prática, a do Signer; quem mantém estranhos de fora é o segredo, não esta cota. Uma entrega limitada recebe `429`, que o Signer tenta de novo como qualquer resposta não 2xx, e a varredura de pastas encontra o que uma nova tentativa deixar para trás. |
As respostas bloqueadas pelo limite de requisições trazem `code = "rate-limited"` no envelope de erro.
## `Hosting`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `Hosting:RequireHttps` | bool | `false` | `Hosting__RequireHttps` | Controla o redirecionamento HTTPS no próprio processo. `false` (padrão) para instalações como serviço e Docker que fazem a terminação TLS em um proxy reverso. Aparece no banner de resumo de prontidão como `https redirect = on/off`. |
| `Hosting:ForwardedHeaders:Enabled` | bool | `false` | `Hosting__ForwardedHeaders__Enabled` | Lê o endereço e o esquema do cliente de `X-Forwarded-For` e `X-Forwarded-Proto`. Desligado por padrão, e, desligado, é byte a byte o produto como foi entregue — nenhum middleware é adicionado. Ligá-lo **exige um conjunto de confiança**: `TrustAnyProxy`, `KnownProxies` ou `KnownNetworks`; caso contrário, o boot é recusado. Aparece no banner de resumo de prontidão como `forwarded headers = …`, indicando o conjunto de confiança em vez de apenas `on`. |
| `Hosting:ForwardedHeaders:TrustAnyProxy` | bool | `false` | `Hosting__ForwardedHeaders__TrustAnyProxy` | Aceita um header encaminhado vindo de **qualquer** endereço anterior na cadeia. É a configuração prevista para o Azure App Service, cujo front-end não tem endereço estável para listar. ⚠️ Em uma implantação com proxy reverso, significa que qualquer pessoa que consiga acessar o Kestrel diretamente pode se apresentar com qualquer endereço de cliente. Não pode ser combinada com as duas chaves abaixo — uma lista ao lado dela seria ignorada, então a combinação é recusada no boot em vez de ser resolvida por precedência. |
| `Hosting:ForwardedHeaders:KnownProxies` | string[] | `[]` | `Hosting__ForwardedHeaders__KnownProxies__0` | Endereços IP simples cujos headers encaminhados são aceitos (`10.4.0.7`, `::1`). Um valor que não seja endereço IP é recusado no boot, com a indicação do valor. **Os padrões de loopback do framework não são mantidos** — quando esta seção define um conjunto de confiança, ele é o conjunto completo, então um proxy neste host precisa estar listado. |
| `Hosting:ForwardedHeaders:KnownNetworks` | string[] | `[]` | `Hosting__ForwardedHeaders__KnownNetworks__0` | Faixas CIDR cujos headers encaminhados são aceitos (`10.4.0.0/16`). Recusada no boot se não for uma faixa CIDR ou se o endereço tiver bits além do comprimento do prefixo — `10.4.0.7/16` descreve `10.4.0.0/16`, e um arquivo que diz uma faixa enquanto o host confia em outra merece fazer o boot falhar. |
| `Hosting:ForwardedHeaders:ForwardLimit` | int | `1` | `Hosting__ForwardedHeaders__ForwardLimit` | Quantas entradas são lidas a partir do fim (à direita) de cada header encaminhado — uma por proxy pelo qual a requisição realmente passa, o que dá `1` tanto para um balanceador de carga de plataforma quanto para um único proxy reverso. Faixa `[1, 16]`; valores fora dela são recusados no boot. O "ilimitado" do framework é deliberadamente inacessível, porque, em uma cadeia ilimitada, a ponta mais distante foi escrita pelo cliente. |
### Por que os headers encaminhados são uma chave do produto, e não apenas a chave do framework
O ASP.NET Core tem a própria chave, `ASPNETCORE_FORWARDEDHEADERS_ENABLED=true`, que confia em
**qualquer** origem anterior, sem forma de restringir isso. Definir tanto ela quanto
`Hosting:ForwardedHeaders:Enabled` é **recusado no boot**, com a indicação das duas: cada uma acrescenta
o processamento de headers encaminhados, então todo header seria processado duas vezes, e um
`ForwardLimit` de um aceitaria silenciosamente dois saltos.
Duas coisas no produto dependem do endereço do cliente, e é por isso que decidir em quais headers
confiar é uma questão de configuração, e não um detalhe:
- **A partição do limite de requisições.** Atrás de um balanceador de carga, todos os chamadores chegam
com um único endereço, então a cota por cliente da rota de aprovação anônima vira uma única cota
compartilhada pelo mundo inteiro.
- **O endereço registrado em uma aprovação**, que é um dos controles compensatórios da rota de
aprovação anônima. Um endereço que, na verdade, é o do balanceador de carga não diz nada sobre quem
decidiu.
No Azure App Service, a configuração prevista é `Enabled = true` com `TrustAnyProxy = true`, e o risco
que isso traz é eliminado restringindo o acesso à origem — veja
[Azure App Service](https://docs.lacunasoftware.com/articles/bulk-signer/azure#entrada--front-door-na-frente-do-app).
## `ApproverSecondFactor`
Um segundo fator TOTP para aprovadores — um código de um aplicativo autenticador, conferido com uma
inscrição por pessoa. **Desligado por padrão**, então a atualização não muda nada. Lido uma vez na
inicialização; ao mudar qualquer coisa, reinicie.
Vale para o host inteiro, e não por perfil de assinatura, e essa escolha é estrutural, não uma
conveniência: uma regra por perfil é congelada no job quando ele fica retido, e a autenticação não pode
estar nesse snapshot — o snapshot registra *qual regra se aplicou*, de modo que editar este arquivo
nunca pode servir para contornar a autorização. Uma chave que vale para o host inteiro não tem nada por
perfil a congelar, então um job que ficou retido antes de o fator ser habilitado não precisa de migração
nem de tratamento especial.
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `ApproverSecondFactor:Enabled` | bool | `false` | `ApproverSecondFactor__Enabled` | Chave geral. Quando falsa, nada muda em nenhuma tela de aprovação, e nenhum valor de `ApproverSecondFactor:*` é lido. Quando verdadeira: o painel de inscrição aparece em `/approvals`, **toda decisão no portal pede um código uma vez por janela de verificação**, e as duas rotas que não têm sessão de navegador — `POST /api/approvals/{id}` e uma decisão a partir de `/approve/{jobId}` — **passam a recusar de imediato**. Essa última parte quebra qualquer ERP que faça aprovações por REST; veja [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals#provando-que-é-você). |
| `ApproverSecondFactor:SeedSecret` | string | — | `ApproverSecondFactor__SeedSecret` | **REQUIRED quando habilitado. SECRET — nunca o versione.** A chave com a qual o seed do autenticador de cada aprovador é criptografado em repouso (PBKDF2-HMAC-SHA256 → AES-256-GCM). Mínimo de 32 caracteres, o mesmo mínimo exigido do `ApproverPortal:LinkSecret`. Ela **criptografa** os seeds; não os deriva — deliberadamente, os seeds são aleatórios por aprovador, para que quem tem o primeiro fator não consiga gerar o segundo. **Perdê-la ou rotacioná-la significa que todos os aprovadores precisam se inscrever de novo.** |
| `ApproverSecondFactor:VerificationWindow` | TimeSpan | `00:20:00` | `ApproverSecondFactor__VerificationWindow` | Por quanto tempo um fator comprovado vale antes de precisar ser comprovado de novo. **Zero é válido e significa "pedir a cada decisão"** — a configuração mais rígida, e não uma desativação. Absoluta, nunca deslizante, e restrita a uma sessão de navegador. Faixa válida: `00:00:00` a `08:00:00`. **Congelada em cada janela no momento em que o código é digitado**, de modo que editá-la vale para as janelas abertas depois e não muda nada nas que já estão abertas. |
| `ApproverSecondFactor:Issuer` | string | `Lacuna Bulk Signer` | `ApproverSecondFactor__Issuer` | O rótulo que um aplicativo autenticador mostra acima do código. Vai no parâmetro `issuer` da URI de provisionamento e é repetido no label dela, porque os aplicativos divergem sobre qual dos dois leem. Defina-o quando um diretório abriga vários ambientes e, sem isso, um aprovador veria duas contas com nomes idênticos. |
> **Aviso: Sempre escreva o componente de dias em VerificationWindow**
>
> Um valor de três componentes é `hh:mm:ss` somente enquanto o primeiro número for 23 ou menos; a partir
> de 24, o binder o lê como *dias*, então `"24:00:00"` significa vinte e quatro dias. Diferentemente do
> `ExpiresAfter`, que aceita isso silenciosamente, aqui o teto de oito horas transforma o valor em uma
> recusa de boot que aponta o erro.
### Três recusas de boot
`Enabled = true` impede a inicialização, indicando a chave, quando:
1. **`SeedSecret` está ausente ou tem menos de 32 caracteres.** Sem ela, as inscrições ficam
ilegíveis, então um fator configurado pela metade não é um controle mais fraco — é um controle cuja
força ninguém definiu.
2. **`VerificationWindow` é negativa ou maior que oito horas.** Além de um turno de trabalho, o valor
deixa de representar a presença de alguém diante do teclado e passa a representar uma sessão — que
é justamente o motivo pelo qual uma janela deslizante foi descartada.
3. **`ApproverPortal:Enabled` é falso *e* nenhuma seção `Auth:EntraId` está configurada.** São os dois
únicos recursos que produzem uma sessão identificada, e o fator é comprovado dentro de um deles. Sem
nenhum dos dois, ninguém conseguiria se inscrever, todo job de um perfil com aprovação configurada
ficaria retido indefinidamente, e nenhum arquivo de pagamento seria assinado.
## `AllowedHosts`
| Chave | Tipo | Padrão | Override por env | Observações |
|-------|------|--------|------------------|-------------|
| `AllowedHosts` | string | `*` | `AllowedHosts` | Filtragem de host padrão do ASP.NET Core. Sobrescreva com uma lista separada por vírgulas se a instalação estiver atrás de um proxy reverso com um nome de host externo fixo. |
## Variáveis de ambiente sem equivalente em JSON
| Variável | Finalidade |
|----------|------------|
| `BULK_SIGNER_CONFIG_DIR` | Indica ao binário onde fica a configuração de produção quando o binário está em um local de instalação somente leitura. Definida pelos scripts de instalação. |
| `BULK_SIGNER_PKCS11_PIN` | O PIN do HSM/token — lido da variável de ambiente cujo nome é configurado em `Signing:Certificate:Pkcs11:PinEnvVar`. |
| `BULK_SIGNER_ENCRYPTION_PASSWORD` | Senha do PBKDF2 — lida da variável de ambiente cujo nome é configurado em `Encryption:PasswordEnvVar`. |
| `APPLICATIONINSIGHTS_CONNECTION_STRING` | Variável padrão do Azure Monitor. Lida diretamente pelo exportador e aceita pelo validador de inicialização, de modo que `Telemetry:ConnectionString` pode ficar sem definir. Veja [Telemetria](https://docs.lacunasoftware.com/articles/bulk-signer/telemetry). |
| `ASPNETCORE_ENVIRONMENT` | Nome de ambiente padrão do ASP.NET Core (`Development`, `Production`). Os scripts de instalação definem `Production`. Com `Production`, o [`Signing:TrustLacunaTestRoot`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingtrustlacunatestroot--certificados-de-teste-para-homologação) é recusado no boot — um host de homologação que confia na raiz de teste roda com outro nome, como `Staging`. |
| `ASPNETCORE_URLS` | Padrão. Os scripts de instalação definem `http://0.0.0.0:8080`. |
| `ASPNETCORE_CONTENTROOT` | Padrão. A instalação no Windows a define como `C:\ProgramData\Lacuna\BulkSigner`, para que os caminhos de arquivo sejam resolvidos em um disco gravável pelo operador. |
## Verificando a configuração em tempo de execução
O banner de resumo de prontidão exibido na inicialização lista as configurações mais importantes para
tomar decisões (modo de host, ambiente, redirecionamento https, content root, raiz de armazenamento,
impressão digital da licença, origem do certificado, política de assinatura, status da criptografia,
intervalo de polling, modo do pipeline e uma linha `operational store` com o provider de banco de
dados). Uma chave digitada errado aparece ali como um valor padrão, em vez do valor que você
pretendia. Outras quatro linhas estão sempre presentes, e cada uma responde a uma pergunta: `trust set`
(se a raiz de teste da Lacuna é confiável — veja
[`Signing:TrustLacunaTestRoot`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingtrustlacunatestroot--certificados-de-teste-para-homologação)),
`web pki license` e `cloudhub` (configurado ou não, nunca o valor) e `customer logo` (`none`, o
logotipo carregado ou o motivo de não ter sido carregado). As linhas `ready` e `metrics` mostram
`(anonymous)` ou `(API key)`, então um `Readiness:RequireApiKey` ou `Metrics:RequireApiKey` que não
foi aplicado fica visível no boot.
Linhas que aparecem somente quando o recurso correspondente está configurado:
| Linha do banner | Aparece quando |
|-----------------|----------------|
| `store status`, `store isolation` | `Database:Provider = SqlServer` |
| `work share`, `azure credential`, `azure shares`, `input providers`, `work share owner` | `Storage:Provider = AzureFiles`, ou qualquer pasta de entrada que o indique |
| `blob=…` em uma linha de perfil | o certificado desse perfil é lido do Azure Blob Storage |
| `cnab240=on`, `approval=N/M`, `expires=…` em uma linha de perfil | `CheckCNAB240` / `Approval` nesse perfil |
| prefixo `DEGRADED · ` em uma linha de perfil, com uma linha `FAIL` ao lado | o certificado desse perfil não abriu |
O `/api/ready` retorna um corpo JSON com cada verificação — banco operacional, cada pasta de entrada,
licença, as linhas `storage-share:` e `work-share-owner` em um compartilhamento de trabalho remoto e uma
linha por perfil de assinatura degradado — com o veredito e sem detalhes; o `/api/ready/details`, com a
chave de API, acrescenta o detalhe de cada verificação. Um `503` com um corpo que lista a verificação
que falhou é o ciclo rápido de feedback para erros de configuração — veja
[Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting).
Algumas linhas informam `ok: false` **sem** transformar a resposta em `503`, porque um `503` tira a
instância do balanceador de carga, e cada uma delas tem uma solução que a própria instância oferece: a
linha do destino de log em tabela do Azure (uma indisponibilidade do log não deve virar uma
indisponibilidade da ingestão de arquivos), a de um perfil de assinatura degradado (é no dashboard que
ele é corrigido), uma linha `profile-input-folder:` para um perfil vinculado a uma pasta que este host
não configura e, no modo cluster, a linha `cluster-instance` de uma instância que se retirou. **O
alerta deve ler `checks[]`, e não só o `ready` de nível superior.**
---
**A seguir:** [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates) — escolhendo e configurando uma origem de certificado.
**Anterior:** [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation).
# [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates)
O Lacuna Bulk Signer assina com certificados X.509 fornecidos por uma de quatro origens. Esta página
explica como escolher uma origem, onde colocar o material do certificado e como encontrar os
thumbprints SHA-1 que a configuração exige.
Cada perfil de assinatura tem o próprio certificado. Os perfis ficam no banco de dados operacional e são
criados e editados nas páginas de perfis de assinatura do dashboard, que pedem exatamente os campos
mostrados abaixo. Os blocos de configuração desta página — o bloco global `Signing:Certificate`
(implantações de certificado único) **ou** cada entrada de `Signing:Profiles[].Certificate`
(implantações com múltiplos perfis — veja
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofiles--perfis-de-assinatura-por-pasta)) — são o **seed
(carga inicial) de uso único** que o primeiro boot importa para o banco. Todas as regras abaixo valem
igualmente para o seed e para a página do perfil.
Cada perfil abre seu certificado uma única vez, no boot, de modo que um erro aparece na inicialização, e
não no primeiro arquivo correspondente. **Um certificado que não abre deixa aquele perfil *degradado*,
mas o host continua em execução**: o motivo aparece no banner de inicialização, no log durável, na
página do perfil e em uma linha própria de readiness, `signing-profile:`; todos os outros perfis
continuam assinando, e os jobs roteados para o perfil com problema falham com `profile.degraded`.
Corrigir o certificado exige reinicialização, porque o handle é aberto uma única vez e nunca é
recarregado.
> **Aviso: Mudou na 2.1.0 — um certificado com problema não impede mais o boot**
>
> Versões anteriores se recusavam a iniciar quando o certificado de qualquer perfil não abria. Agora que os
> perfis são editados pelo dashboard, recusar o boot tiraria do ar a página em que a correção é feita —
> então o perfil passa a ser informado como degradado e o resto da implantação continua assinando.
## Escolhendo uma origem
| Origem | Use quando | Evite quando |
|--------|------------|--------------|
| `Pfx` | A chave privada é exportável e está armazenada como um arquivo `.pfx`/`.p12` em disco. | A política de aquisição proíbe chaves exportáveis (nesse caso, use HSM ou repositório). |
| `Pkcs11` | A chave fica em um HSM, smart card ou token USB com um driver PKCS#11 do fabricante. A política de auditoria exige que a chave nunca saia do dispositivo. | Instalações em container nas quais o driver do fabricante não pode ser montado; alvos que não são Windows, quando o fabricante só fornece driver para Windows. |
| `WindowsStore` | Alvos Windows nos quais o certificado foi importado previamente para o repositório de certificados. | Alvos Linux ou Docker — o validador recusa esta origem em hosts que não são Windows. |
| `AzureKeyVault` | A chave nunca pode tocar o host, mas um HSM on-premises não é uma opção — o Azure guarda a chave e assina remotamente. Funciona em todos os alvos, inclusive Docker. | Instalações isoladas da rede (air-gapped), ou quando a latência de rede de cada assinatura até o Azure for inaceitável. |
A forma como a identidade de assinatura é *selecionada* varia conforme a origem, e a diferença importa
sempre que um token, repositório ou cofre contém mais de uma identidade:
| Origem | Identidade selecionada por |
|--------|----------------------------|
| `Pfx` | Nada a selecionar — o arquivo contém uma única identidade. |
| `Pkcs11`, `WindowsStore` | **Thumbprint SHA-1.** A seleção pelo subject nunca é usada, porque tokens e repositórios costumam conter várias identidades, e uma regra de "o primeiro que corresponder" tornaria a trilha de auditoria desonesta. |
| `AzureKeyVault` | O **nome da chave** no cofre, para a chave privada, mais um `.cer` para o certificado público. O par é conferido no boot. |
As duas origens que apontam para um *arquivo* — `Pfx` e `AzureKeyVault` — podem ler esse arquivo do
[Azure Blob Storage](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#lendo-o-arquivo-de-um-blob) em vez do disco local, o que as torna utilizáveis em
um host sem sistema de arquivos durável.
Um PKCS#12 pode estar ainda em um **terceiro** lugar: [enviado pelo dashboard](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#enviando-o-arquivo-pelo-dashboard)
e guardado no banco operacional, para o operador que não tem nenhum acesso ao sistema de arquivos do
host. Os três locais são mutuamente exclusivos, e qualquer combinação é recusada — um caminho, um blob e
um envio nunca são combinados nem resolvidos por precedência, porque *qual certificado assinou* não pode
depender de qual local por acaso estava legível.
Um tipo de certificado deliberadamente **não** está neste mapa: aquele com que um **aprovador** coassina
um arquivo de pagamento, que fica com o aprovador e nunca é configurado no host. Veja
[O certificado do aprovador](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#o-certificado-do-aprovador).
## ICP-Brasil e ADR-Básica
O Bulk Signer foi projetado para cenários compatíveis com a ICP-Brasil. A política de assinatura padrão
aplicada pelos assinadores é a **ADR-Básica** (Assinatura Digital de Referência — Básica), a política
de referência do catálogo de políticas do ITI. A ADR-Básica cobre CAdES (`.p7m`), PAdES (PDF) e XAdES
(XML) e é o padrão correto para notas fiscais, contratos e outros documentos transacionais.
| Conceito | Onde ler mais |
|----------|---------------|
| ITI (Instituto Nacional de Tecnologia da Informação) — o órgão responsável pelas políticas | [gov.br/iti](https://www.gov.br/iti/pt-br) |
| Lista de ACs (Autoridades Certificadoras) credenciadas na ICP-Brasil | [Entidades ICP-Brasil](https://www.gov.br/iti/pt-br/assuntos/icp-brasil/entidades-icp-brasil) |
| Políticas de assinatura (ADR-Básica, ADR-T, ADR-V, ADR-C, ADR-A) | Consulte as versões atuais no site de políticas do ITI antes de qualquer implantação que precise de política diferente da padrão. |
| Documentação do Lacuna PKI SDK | [docs.lacunasoftware.com](https://docs.lacunasoftware.com/pt-br/articles/pki-sdk/index.html) |
O Bulk Signer não inclui, recomenda nem endossa nenhuma AC comercial específica. Você adquire
certificados ICP-Brasil de qualquer AC/AR (Autoridade Certificadora / Autoridade de Registro) da lista
oficial do ITI, conforme a sua própria política de aquisição. Depois de emitido, o certificado, com a
chave privada, chega como um arquivo PFX (para certificados protegidos por software) ou pré-instalado em
um HSM ou token criptográfico (para os protegidos por hardware) — e a partir daí vale a matriz de
configuração abaixo.
### Um P7S simples em vez da ADR-Básica
> **Dica: Novo na 2.17.0**
>
> Desligado por padrão; nada muda para um perfil que não o ligue.
Qualquer perfil CAdES pode deixar a ADR-Básica de lado individualmente, para um destinatário que não
consegue lê-la: a opção [`SimpleP7s`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingprofiles--perfis-de-assinatura-por-pasta)
do perfil faz a chave dele assinar um **P7S simples** — um CMS anexado com **SHA-1**, sem LCRs no
envelope, sem o atributo signing-certificate e sem política de assinatura. A cadeia de certificação
continua no envelope, um perfil com `Verify` ligado continua verificando a saída antes de o arquivo de
entrada ser excluído (com uma política compatível com a assinatura, já que o validador da ICP-Brasil
recusa uma assinatura sem política), e o certificado é validado exatamente contra as mesmas ACs raiz que
a ADR-Básica exigiria, dentro do [conjunto de
confiança](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#certificados-de-teste-e-o-conjunto-de-confiança) do host — então o mesmo certificado
ICP-Brasil serve nos dois casos.
O SHA-1 exige uma chave **RSA**, e outro tipo de chave é recusado com uma mensagem que diz isso. Exige
também uma chave com a qual o host consiga assinar SHA-1: uma chave no [Azure Key
Vault](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#origem--azurekeyvault) nunca consegue, então essa combinação é recusada ao salvar, e um
dispositivo PKCS#11 em modo FIPS pode não conseguir. O SHA-1 é um resumo obsoleto, e um P7S simples é uma
evidência mais fraca que a ADR-Básica em todos os pontos que um validador confere; por isso, use-o só nos
perfis que alimentam o destinatário que precisa dele. Ele não está disponível nos métodos do Lacuna
Signer, em que é o Signer que escolhe a política.
Onde os aprovadores do perfil assinam, as assinaturas deles também são P7S simples (2.18.0): toda
assinatura de um job assim é feita sob a política que o job congelou ao ficar retido; por isso, o
[certificado do aprovador](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#o-certificado-do-aprovador) também precisa ser RSA, e um provedor em nuvem
que receba um pedido de assinatura SHA-1 pode recusá-lo. Veja
[Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals#o-conjunto-de-assinantes).
Todas as superfícies mostram a política realmente em vigor: a linha do perfil no banner de inicialização
traz `policy=simple-p7s (SHA-1)`, a linha `signing policy` do resumo de prontidão lista os perfis que
assinam um P7S simples, a página do perfil mostra o chip **P7S simples (SHA-1)**, o `GET /api/profiles`
informa `simpleP7s`, e o histórico de todo job assinado assim o indica — `Signing complete as a simple
P7S (SHA-1); verifying output.` Uma linha armazenada que as regras recusariam assina ADR-Básica e nunca
aparece como P7S simples.
### Certificados de teste e o conjunto de confiança
A Lacuna publica uma **PKI de teste** para desenvolvimento e homologação sem um certificado real: uma AC
raiz (*Lacuna Root Test v3*), uma AC (*Lacuna CA Test v7*) e um pacote de certificados ICP-Brasil
simulados, dos quais os mais usados são *Alan Mathison Turing* e *Pierre de Fermat*, com a senha `1234`.
Eles estão descritos nos
[exemplos do PKI SDK](https://github.com/LacunaSoftware/PkiSdkSamples/blob/master/TestCertificates.md)
da Lacuna. Eles contêm um CPF e são validados como os certificados reais, **mas sob uma AC raiz que não
é uma autoridade de verdade** — e esse é justamente o propósito deles.
Se um host os aceita ou não depende do seu **conjunto de confiança**: as ACs raiz sob as quais toda
assinatura é feita, com base nas quais ela é conferida depois, e às quais o certificado de um aprovador é
submetido antes do pedido de PIN. Há um conjunto por host, o mesmo para todos os perfis, e o banner de
inicialização o exibe na linha `trust set`:
| `Signing:TrustLacunaTestRoot` | Conjunto de confiança | Turing / Fermat |
|-------------------------------|-----------------------|-----------------|
| não definida (o padrão) | `production` — somente as ACs raiz da ICP-Brasil, tal como vêm nas políticas ICP-Brasil do SDK; nada do sistema operacional | **Recusados.** Um job assinado com um deles falha; um aprovador que apresente um é recusado como `approval.certificate-invalid`. |
| `true` | `production + Lacuna test root` — ICP-Brasil, o conjunto de confiança do Windows do SDK (o repositório de certificados da máquina, no Windows) e a AC raiz de teste | Aceitos |
A chave existe para um **host de homologação que roda a imagem publicada** e precisa dos certificados de
teste em vez de um e-CPF real para cada aprovador. Ela é **recusada no boot quando o nome do ambiente é
`Production`**: defina `ASPNETCORE_ENVIRONMENT=Staging` (ou qualquer outro nome) naquele host, ou o boot
falha indicando a chave e a solução. Assim, um host de produção que herdou essa configuração se recusa a
iniciar em vez de confiar em uma raiz que qualquer pessoa pode baixar. Um host com a chave definida
também registra um aviso no log a cada boot. Veja [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signing) para a chave em si.
> **Observação: Novo na 2.3.0**
>
> Antes da 2.3.0, um build de release não tinha como confiar na AC raiz de teste da Lacuna.
## Origem = Pfx
```json
"Signing": {
"Certificate": {
"Source": "Pfx",
"Pfx": {
"Path": "/etc/bulksigner/signing.pfx",
"Password": ""
}
}
}
```
(Prefira a variável de ambiente `Signing__Certificate__Pfx__Password` a um valor no arquivo de
configuração.)
### Onde colocar o arquivo
Coloque o arquivo `.pfx` em um local:
- Legível pela conta de serviço: `bulksigner` no Linux, `NT SERVICE\LacunaBulkSigner` no Windows, UID
1654 no container Docker.
- Não legível por outros usuários do host. No Linux:
`chown bulksigner:bulksigner signing.pfx && chmod 0640 signing.pfx`. No Windows, a ACL que o script
de instalação aplica em `ProgramData` é suficiente.
- Fora do controle de versão.
### Tratamento da senha
A senha pode ficar em `Signing:Certificate:Pfx:Password` no `appsettings.Production.json` (que está no
gitignore) ou — preferencialmente — na variável de ambiente
`Signing__Certificate__Pfx__Password`. Uma string vazia é permitida para fixtures de teste sem senha;
arquivos PFX de produção devem sempre ter senha.
Esses são os locais do **seed**. Depois de importada — ou quando o perfil é criado pelo dashboard —, a
senha é armazenada com o perfil, criptografada com a `Signing:ProfileSecretsKey`, um segredo do host
mantido fora do banco de dados; a página do perfil a mostra apenas como *configurado* ou *não
configurado*. Uma implantação cujos perfis tenham qualquer segredo (uma senha de PKCS#12, um segredo de
aplicativo do Azure Key Vault, uma credencial de blob, um arquivo enviado) precisa dessa chave definida
antes do primeiro boot que os importar. Veja
[Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security#segredos-das-origens-de-certificado).
### Verificando se o arquivo é carregável
**Exporte com o envelope clássico.** A biblioteca de assinatura abre o envelope PKCS#12 clássico
(PBE-SHA1-3DES e RC2), e não PBES2 com AES. O PBES2 é o que o `openssl pkcs12 -export` grava **por
padrão** no OpenSSL 3, e o que uma exportação do Windows grava quando se escolhe **AES256-SHA256** em vez
do padrão TripleDES-SHA1. Um arquivo exportado assim resulta em um perfil degradado cujo motivo menciona
o PBES2 e a correção. No OpenSSL, exporte com `-legacy` desde o início; no Windows, mantenha o assistente de
exportação ou o `Export-PfxCertificate` em TripleDES-SHA1. O OpenSSL 1.x sempre gravou o envelope
clássico; um arquivo recusado por PBES2 pode ser reexportado pelo OpenSSL:
```bash
openssl pkcs12 -in moderno.pfx -nodes -passin pass: -out tmp.pem
openssl pkcs12 -export -legacy -in tmp.pem -passout pass: -out signing.pfx
shred -u tmp.pem # a chave privada fica em claro no tmp.pem
```
Antes de apontar o Bulk Signer para ele, confirme que o arquivo é decifrado com a senha que você
pretende configurar. No OpenSSL 3, a flag `-legacy` é necessária para *ler* um arquivo de envelope
clássico, e não só para gravá-lo:
```bash
# Linux / Mac
openssl pkcs12 -legacy -in signing.pfx -nokeys -info -passin pass:
```
```powershell
# Windows — carregue em um objeto de certificado transitório
$pwd = ConvertTo-SecureString -String '' -AsPlainText -Force
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new("signing.pfx", $pwd)
$cert.Thumbprint
```
O comando do Windows exibe o thumbprint SHA-1 como efeito colateral — você vai precisar dele para a
origem WindowsStore se importar o mesmo certificado depois, mas a origem Pfx **não** exige um
thumbprint (o arquivo contém uma única identidade).
Quando um PKCS#12 não abre, o motivo do perfil degradado explica a causa com as palavras do próprio
produto, seguidas da frase original da biblioteca de assinatura:
| O motivo começa com | Causa e correção |
|---------------------|------------------|
| *PFX … did not open with the PKCS#12 password given* | A senha no perfil está errada — ou o arquivo está danificado, o que um PKCS#12 não consegue distinguir de uma senha errada. Confira a senha; se estiver certa, forneça o arquivo de novo. A senha em si nunca aparece na mensagem. |
| *PFX … is encrypted with PBES2, which the signing library does not open* | O envelope moderno. Reexporte com o clássico, como acima. |
| *Certificate … is empty (0 bytes)* | Um arquivo de zero bytes no caminho, no blob ou no envio. Forneça o arquivo de novo. |
## Considerações de concorrência por origem
`Pipeline:MaxConcurrency > 1` permite que o worker processe vários jobs de assinatura em paralelo. Se
isso é *seguro* ou não depende do modelo de segurança de thread da origem do certificado — todas as
tarefas de assinatura criadas compartilham o certificado carregado. Escolher a combinação errada pode
causar travamentos silenciosos (deadlock) ou erros específicos do fabricante.
| Origem | Segura para threads com assinaturas concorrentes? | `MaxConcurrency` recomendado |
|--------|---------------------------------------------------|------------------------------|
| **Pfx** | Sim (a chave é mantida em memória). | Até o limite de 32; o ponto ideal costuma ser 4–8 em implantações com PFX. |
| **Pkcs11** | **Geralmente não.** A maioria dos tokens de uso pessoal oferece uma única sessão por login; chamadas de assinatura concorrentes travam ou falham. HSMs de servidor costumam aceitar várias sessões, mas a quantidade depende do fabricante. | `1`, a menos que a documentação do fabricante declare explicitamente suporte a sessões concorrentes e você tenha feito medições. |
| **WindowsStore** | Depende do fabricante. CSPs de software costumam ser seguros para threads; CSPs baseados em smart card variam. | `1` por padrão; aumente somente depois de verificar que o provider funciona bem com chamadas concorrentes. |
| **AzureKeyVault** | Sim. Cada assinatura é uma chamada HTTPS independente e sem estado — não há sessão a disputar. | Até o limite de 32. Fique atento ao throttling HTTP 429 do Azure, e não a travamentos. |
O serviço emite um aviso na inicialização quando `MaxConcurrency > 1` é configurado junto com
`Source = Pkcs11` ou `Source = WindowsStore`. O `AzureKeyVault` deliberadamente **não** gera aviso, pelo
motivo explicado na tabela acima:
```text
[WARN] Pipeline:MaxConcurrency = 4 with Signing:Certificate:Source = Pkcs11 — verify your
token / CSP allows concurrent sessions or set MaxConcurrency = 1.
```
Se você ignorar o aviso e o token não aceitar sessões concorrentes, o sintoma serão jobs em andamento
travados indefinidamente ou falhando com o erro de estado de sessão do fabricante. Veja
[Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting) para o roteiro de diagnóstico.
## Origem = Pkcs11
```json
"Signing": {
"Certificate": {
"Source": "Pkcs11",
"Pkcs11": {
"ModulePath": "/usr/lib/softhsm/libsofthsm2.so",
"Thumbprint": "0123456789abcdef0123456789abcdef01234567",
"PinEnvVar": "BULK_SIGNER_PKCS11_PIN"
}
}
}
```
### Caminho do módulo
Caminho absoluto do driver PKCS#11 do fabricante. Exemplos (fornecidos pelo operador):
| Fabricante / dispositivo | Linux | Windows |
|--------------------------|-------|---------|
| SoftHSM v2 (testes) | `/usr/lib/softhsm/libsofthsm2.so` | n/a |
| SafeNet eToken / Authentication Client | `/usr/lib/x86_64-linux-gnu/pkcs11/libeToken.so` | `C:\Windows\System32\eTPKCS11.dll` |
| Thales SafeNet HSM (PCI) | (caminho fornecido pelo fabricante) | (caminho fornecido pelo fabricante) |
| Smart card Gemalto / Thales IDPrime | (caminho fornecido pelo fabricante) | `C:\Windows\System32\IDPrimePKCS11.dll` |
| Yubico YubiHSM 2 | `/usr/local/lib/pkcs11/yubihsm_pkcs11.so` | (caminho fornecido pelo fabricante) |
O Bulk Signer não inclui drivers de fabricantes. Instale o driver no host antes de apontar a
configuração para ele. Em alvos Docker, monte o `.so` do fabricante no container via `volumes:` —
exemplos comentados estão em `deploy/docker/docker-compose.yml`.
### Encontrando o thumbprint
O thumbprint configurado precisa corresponder a um certificado visível ao driver configurado. Use o
`pkcs11-tool` (do pacote `opensc` — presente na imagem Docker):
```bash
# Linux: lista os certificados no token, com seus thumbprints SHA-1
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --list-objects --type cert --login --pin
```
Para cada certificado listado, calcule o thumbprint SHA-1 exportando o DER e calculando o hash:
```bash
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --read-object --type cert --id --login --pin --output-file cert.der
openssl dgst -sha1 cert.der
# → SHA1(cert.der)= 0123456789abcdef0123456789abcdef01234567
```
Copie esse valor hexadecimal em minúsculas (sem espaços e sem dois-pontos) para
`Signing:Certificate:Pkcs11:Thumbprint`.
### Tratamento do PIN
O PIN **nunca** fica em um arquivo de configuração — o validador impede o boot se uma chave `Pin`
aparecer em `Signing:Certificate:Pkcs11`. Defina a variável de ambiente indicada em `PinEnvVar`
(padrão `BULK_SIGNER_PKCS11_PIN`). Por alvo:
- **Linux:** `BULK_SIGNER_PKCS11_PIN=` em `/etc/bulksigner/bulksigner.env`.
- **Windows:** `[Environment]::SetEnvironmentVariable("BULK_SIGNER_PKCS11_PIN", "", "Machine")`.
- **Docker:** `BULK_SIGNER_PKCS11_PIN=` em `deploy/docker/.env`.
Veja [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) para o tratamento de segredos como um todo.
### Exemplo de montagem no Docker
```yaml
# deploy/docker/docker-compose.yml
services:
bulksigner:
# ...
volumes:
- ./config/appsettings.Production.json:/app/appsettings.Production.json:ro
- ./data:/var/lib/bulksigner
- ./logs:/var/log/bulksigner
# Driver PKCS#11 do fabricante (descomente e ajuste conforme seu HSM):
- /usr/lib/softhsm:/usr/lib/softhsm:ro
# Ou, para um SafeNet eToken no host:
# - /usr/lib/x86_64-linux-gnu/pkcs11:/usr/lib/x86_64-linux-gnu/pkcs11:ro
# Tokens USB também precisam de acesso ao PCSC:
- /var/run/pcscd/pcscd.comm:/var/run/pcscd/pcscd.comm
environment:
- BULK_SIGNER_PKCS11_PIN=${BULK_SIGNER_PKCS11_PIN}
```
A imagem é Debian-slim e traz `libpcsclite1` + `opensc`, de modo que as ferramentas de smart card
funcionam sem configuração adicional. A maioria das bibliotecas `.so` de fabricantes não é compatível com musl, razão
pela qual a imagem não é baseada em Alpine.
## Origem = WindowsStore
```json
"Signing": {
"Certificate": {
"Source": "WindowsStore",
"WindowsStore": {
"StoreLocation": "LocalMachine",
"StoreName": "My",
"Thumbprint": "0123456789ABCDEF0123456789ABCDEF01234567"
}
}
}
```
Somente Windows. Em hosts que não são Windows, o validador gera erro na inicialização.
### StoreLocation: CurrentUser vs LocalMachine
O serviço Windows roda com a conta virtual `NT SERVICE\LacunaBulkSigner`. Essa conta tem seu
próprio repositório `CurrentUser` — ele **não** é o repositório `CurrentUser` do operador. A regra mais
simples:
| Você importou o certificado como… | Use |
|-----------------------------------|-----|
| Computador Local (para toda a máquina, via `certlm.msc` ou `Import-Certificate -CertStoreLocation Cert:\LocalMachine\My`) | `LocalMachine` + conceda à conta virtual acesso à chave privada |
| Seu próprio usuário (via `certmgr.msc` ou `Import-PfxCertificate -CertStoreLocation Cert:\CurrentUser\My`) | Mova-o para `LocalMachine` primeiro — o serviço não o enxergará no seu `CurrentUser` |
Para conceder à conta virtual acesso a uma chave privada em `LocalMachine\My`, abra o `certlm.msc`,
clique com o botão direito no certificado, escolha **Todas as Tarefas → Gerenciar Chaves Privadas…**,
adicione `NT SERVICE\LacunaBulkSigner` e conceda **Leitura**.
### Encontrando o thumbprint
No PowerShell, no host do serviço:
```powershell
Get-ChildItem -Path Cert:\LocalMachine\My | Format-Table Thumbprint, Subject, NotAfter
```
A coluna Thumbprint traz o SHA-1 em hexadecimal. Remova os espaços antes de copiar para a
configuração; maiúsculas e minúsculas não importam (o validador compara o hexadecimal sem diferenciá-las).
## Origem = AzureKeyVault
```json
"Signing": {
"Certificate": {
"Source": "AzureKeyVault",
"AzureKeyVault": {
"Endpoint": "https://my-vault.vault.azure.net/",
"AppId": "8f2c1b3e-1111-2222-3333-444455556666",
"AppSecret": "",
"KeyName": "bulk-signer-signing-key",
"CerPath": "/etc/bulksigner/certificates/signer.cer"
}
}
}
```
(Prefira a variável de ambiente `Signing__Certificate__AzureKeyVault__AppSecret` a um valor no arquivo
de configuração.)
A chave privada é um objeto **key** do Key Vault e nunca sai do Azure: cada assinatura envia um digest
ao cofre e recebe a assinatura de volta. O **certificado público** correspondente é um arquivo `.cer`
local — coloque-o onde você teria colocado o `.pfx`. Esse arquivo contém apenas material público,
então não precisa de proteção além da garantia de integridade.
Esta é a modalidade *somente chave*. Objetos **certificate** hospedados no cofre deliberadamente não são
aceitos: um certificado do cofre ainda precisaria ser baixado para o host para ser usado, o que anula o
motivo de escolher o Key Vault.
### Configuração no Azure
Se você está partindo de um PFX existente, o script `Import-PfxToKeyVault.ps1` na página
[Exemplos](https://docs.lacunasoftware.com/articles/bulk-signer/samples#powershell-7--import-pfxtokeyvaultps1) executa todos os passos abaixo de uma só
vez — importa a chave como não exportável, grava o `.cer`, registra o aplicativo, concede a ele
permissão de assinatura, verifica o par e exibe o bloco de configuração pronto para colar.
A seguir, os passos manuais, para os casos que o script não cobre (uma chave gerada dentro do cofre, ou
um certificado emitido por uma AC a partir de uma CSR).
1. **Crie ou importe a chave.** No cofre de destino, crie uma chave (RSA 2048+ ou EC) — ou importe
uma. Anote o **nome**; ele será o `KeyName`. Precisa ser um objeto key, e não um objeto certificate.
2. **Registre um aplicativo.** No Microsoft Entra ID, registre um aplicativo e anote o
**Application (client) ID** (`AppId`). Em **Certificates & secrets**, crie um client secret e anote o
valor (`AppSecret`) — o Azure o exibe uma única vez.
3. **Conceda acesso ao cofre.** Dê a esse registro de aplicativo permissão para *obter* a chave e para
*assinar* com ela. Em um cofre com RBAC, a role interna **Key Vault Crypto User** cobre as duas; em
um cofre com políticas de acesso, conceda a permissão de chave **Get** e a operação criptográfica
**Sign**. Nada mais é necessário — o Bulk Signer nunca cria, encapsula (wrap) nem exporta chaves.
4. **Obtenha o certificado.** Gere uma CSR a partir da chave do cofre, peça à sua AC que emita o
certificado e salve o certificado emitido como um `.cer` (DER ou PEM) em `CerPath`.
### O certificado e a chave precisam corresponder
No boot, o Bulk Signer compara a chave pública do `.cer` com a chave pública do cofre e, se elas
diferirem, se recusa a usar o par — o perfil sobe degradado com este motivo:
```text
Certificate '/etc/bulksigner/certificates/signer.cer' does not match Azure Key Vault key
'bulk-signer-signing-key' — their public keys differ. Point CerPath at the certificate issued
for this key, or correct KeyName.
```
Esse é o tipo de falha que um desenho com dois artefatos favorece: renovar um certificado com uma
*nova* chave do cofre enquanto o `KeyName` ainda aponta para a antiga, ou vice-versa. Sem a
verificação, o serviço subiria normalmente e produziria assinaturas que nenhum verificador conseguiria
validar. Com ela, a divergência vira um perfil degradado cujo motivo identifica as duas metades do par.
### Verificando o par antes de implantar
Para confirmar que um `.cer` e uma chave do cofre formam um par sem iniciar o serviço, compare as
chaves públicas com a CLI do Azure e o OpenSSL:
```bash
# Chave pública como registrada no certificado
openssl x509 -in signer.cer -noout -pubkey
# Chave pública como mantida pelo cofre
az keyvault key download --vault-name my-vault --name bulk-signer-signing-key --encoding PEM --file -
```
Os dois blocos PEM precisam ser idênticos byte a byte.
### Tratamento da credencial
O `AppSecret` é um client secret do Entra ID. Diferentemente do PIN do PKCS#11, ele *pode* ficar em um
arquivo de configuração, mas o recomendado é usar a variável de ambiente:
```bash
export Signing__Certificate__AzureKeyVault__AppSecret='…'
```
Ele é registrado nas duas camadas de mascaramento de log, de modo que é removido do log durável, apareça
ele como propriedade estruturada ou interpolado em uma mensagem de exceção. Faça a rotação no Azure e
reinicie o serviço. Veja [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security#credenciais-do-azure-key-vault) para o quadro
completo.
### Rede e throttling
Cada assinatura é uma chamada HTTPS de saída, então o host precisa de uma rota de rede confiável até
`*.vault.azure.net` (e até `login.microsoftonline.com`, para a obtenção de tokens). A latência do
cofre é somada à etapa de assinatura de cada job. Uma indisponibilidade do cofre **paralisa** o
pipeline em vez de corrompê-lo — os jobs afetados falham com o erro do Azure e aceitam nova tentativa
quando o acesso for restabelecido.
A concorrência é segura (veja a tabela acima), mas um `MaxConcurrency` alto por períodos prolongados pode
provocar respostas de throttling HTTP 429 do Azure. Elas aparecem como jobs com falha contendo o erro do
Azure, e não como travamentos.
## Lendo o arquivo de um blob
Um host **sem disco local durável** — um container, um App Service, um pod do AKS — não tem onde
guardar um `.pfx` ou um `.cer`. Incluí-lo na imagem funciona, mas transforma a renovação do
certificado em um novo build da imagem e coloca material de certificado no seu registry. Por isso, as
duas origens que apontam para um arquivo podem, em vez disso, apontar para um blob no Azure Blob Storage:
```json
"Signing": {
"Certificate": {
"Source": "Pfx",
"Pfx": {
"Password": "",
"Blob": {
"Url": "https://contoso.blob.core.windows.net/certificates/signer.pfx",
"Credential": "ManagedIdentity"
}
}
}
}
```
O `Path` é omitido — **exatamente um entre `Path` e `Blob`: nunca os dois, nunca nenhum.** O mesmo
bloco funciona em `AzureKeyVault` (contendo o `.cer`, no lugar de `CerPath`) e em qualquer entrada de
`Signing:Profiles[].Certificate`.
| Chave | Obrigatória | Observações |
|-------|-------------|-------------|
| `Url` | sim | A URL completa do blob — exatamente o que o botão **Copy URL** do portal fornece. Uma URL com **query string é recusada no boot**: é assim que chega uma shared access signature (SAS), e SAS não é uma credencial aceita. Por causa dessa regra, a URL nunca é secreta, e por isso aparece inteira no banner de inicialização. |
| `Credential` | sim | `ManagedIdentity`, `ServicePrincipal` ou `AccountKey`. **Não tem valor padrão** — usar a identidade Azure do próprio host sem que ninguém peça autenticaria como uma identidade que ninguém indicou. |
| `TenantId`, `AppId`, `AppSecret` | somente `ServicePrincipal` | O `TenantId` é obrigatório mesmo quando o `AppId` indica o mesmo aplicativo do Entra que o bloco `AzureKeyVault` ao lado: esse bloco não tem chave de tenant, e **nada aqui é herdado**. |
| `AccountKey` | somente `AccountKey` | Gera um aviso na inicialização. Veja abaixo. |
Como é você quem informa o host na URL, um **endpoint de nuvem soberana funciona sem configuração
extra** — informe o endpoint que você de fato usa.
### De que a credencial precisa
Para `ManagedIdentity` e `ServicePrincipal`, conceda à identidade a role **Storage Blob Data Reader** no
container (ou na conta). Acesso de leitura a um blob é tudo de que isso precisa — nada no Bulk Signer
grava, lista, move ou faz lease de um blob. O `ManagedIdentity` aceita **somente identidade atribuída
pelo sistema**; um host fora do Azure não tem endpoint de identidade algum.
### O `AccountKey` e o que ele custa
Uma chave de conta concede **acesso total ao plano de dados da conta de armazenamento inteira** e não
pode ter o escopo reduzido nem prazo de expiração. Ela é aceita mesmo assim, porque uma implantação `Pfx`
on-premises pode não ter acesso algum a um tenant do Microsoft Entra — e, diferentemente do
`AzureKeyVault`, que nem funciona sem acesso ao Entra, esse host não tem outra opção.
O aviso de inicialização, portanto, diz coisas diferentes conforme o que o blob contém:
| Blob em | O que ele contém | O que uma `AccountKey` vazada expõe |
|---------|------------------|-------------------------------------|
| `AzureKeyVault:Blob` | o `.cer` — material público | um certificado público; a chave privada permanece no cofre |
| `Pfx:Blob` | o arquivo PKCS#12 | **a chave de assinatura** |
> **Perigo**
>
> Se você tem acesso a um tenant, use `ManagedIdentity` ou `ServicePrincipal` — especialmente para um
> PFX. O `Pfx:Blob` é a **única** configuração deste produto na qual material de chave privada trafega
> pela rede; `Pkcs11` e `AzureKeyVault` existem justamente para impedir isso, e a existência dela não
> enfraquece nenhum dos dois.
### O que isso não muda
- **O arquivo é lido uma única vez, no boot.** Um blob renovado exige reinicialização, exatamente como
um arquivo local renovado. Nada faz polling do blob.
- **Um blob inacessível deixa aquele perfil degradado, mas o host continua em execução** — como qualquer
outro certificado que não abre: o problema é informado no banner, no log durável e em uma linha de
readiness, e os jobs roteados para o perfil falham com `profile.degraded` enquanto todos os outros
perfis continuam assinando. O que **continua** impedindo o boot é um bloco `Blob` malformado
*declarado na configuração* (o seed), como uma URL com query string ou sem `Credential`, porque isso é
um erro no arquivo de configuração, e não em um perfil armazenado.
- **A senha do PFX não pode ser buscada de lugar nenhum.** Ela continua sendo um valor de configuração,
que pode ser sobrescrito por variável de ambiente. Uma senha obtida do mesmo lugar que o arquivo que
ela abre não é um segundo fator.
- **Nada muda quanto ao local da assinatura.** Com `Pfx`, a chave continua sendo carregada na memória
deste host e a assinatura continua local; com `AzureKeyVault`, a chave continua sem nunca sair do
cofre. Colocar o arquivo em um blob diz respeito apenas a onde os bytes ficam armazenados, e nada mais.
> **Aviso: Mudou na 2.1.0 — um blob inacessível não impede mais o boot**
>
> Um blob inacessível impedia o host de iniciar. Desde que os perfis passaram para o banco operacional, um
> perfil que existe e não consegue assinar é um estado comum, e recusar o boot tiraria do ar a página em
> que a correção é feita.
O banner de inicialização mostra o blob na linha do perfil, para que você possa confirmar qual objeto
este processo de fato usou, e não qual objeto o arquivo de configuração indica agora:
```text
signer cades · cert=AzureKeyVault · blob=contoso/certificates/signer.cer · verify=on · …
```
## Enviando o arquivo pelo dashboard
Tanto um caminho quanto um blob pressupõem que você consegue *colocar um arquivo em algum lugar que o
host vai ler*. O operador responsável pelas assinaturas de um departamento muitas vezes não consegue: a
árvore de binários é somente leitura, a imagem é gerada por outra equipe, ou o host é uma instância do
App Service sem disco durável. Nesse caso, um PKCS#12 pode ser entregue ao produto **no navegador** —
**Novo perfil** na página de perfis de assinatura do dashboard, `Pfx` como origem e, então, **Enviar
agora** como local — e os bytes são armazenados no banco operacional, e não no sistema de arquivos do
host.
- **Somente `Pfx`.** Um token e o repositório de certificados do host guardam uma chave que nunca foi um
arquivo, então não há nada a enviar; e o arquivo da origem `AzureKeyVault` é o `.cer` *público*, cuja
metade privada fica no cofre.
- **O banco passa a ser a fronteira de custódia, e esse é o custo.** Os bytes são criptografados com a
`Signing:ProfileSecretsKey` antes de serem gravados, e essa chave é mantida **fora** do banco de dados
— essa é a essência do desenho, porque o que ela protege está *dentro* do banco. Depois que uma chave
privada é enviada, uma cópia do arquivo do banco ou uma connection string vazada equivale a uma
credencial de assinatura roubada, a menos que a chave esteja em outro lugar. Tenha isso em mente em
qualquer implantação cujo banco tenha backup, seja replicado ou copiado para a máquina de um
desenvolvedor — e observe que o
[backup do banco de dados](https://docs.lacunasoftware.com/articles/bulk-signer/retention#a-funcionalidade-de-backup-embutida--somente-sqlite) embutido
grava um artefato sem criptografia, então uma chave enviada viaja nele protegida somente pela
`Signing:ProfileSecretsKey`.
- **O disco local foi descartado de propósito, e não por esquecimento.** Gravar o envio no sistema de arquivos do host falha
justamente na implantação para a qual a funcionalidade existe: o disco de uma instância em cluster é
efêmero, então o arquivo se perderia na reciclagem e nunca chegaria às outras instâncias.
- **O limite é de 256 KiB**, e a página recusa um arquivo maior antes de lê-lo.
- **Ele nunca mais pode ser lido.** Não há download, e nenhuma página ou endpoint mostra os bytes ou a
senha; os dois aparecem como *configurado* ou *não configurado*. Guarde sua própria cópia do `.pfx` em
um lugar que você controla — o banco é onde o produto o mantém, e não um depósito do qual recuperá-lo.
- **As três opções do formulário de edição são manter, substituir e descartar.** O **Editar
certificado** de um perfil existente oferece o envio como local, tenha o perfil um arquivo ou não, com
um seletor logo abaixo e uma nota explicando o que o salvamento vai fazer: um seletor vazio **mantém** o
arquivo armazenado, um preenchido o **substitui**, e apontar o perfil para um caminho ou um blob o
**remove**. A remoção é avisada antes de salvar, e nenhuma das três acontece por omissão.
- **Um perfil novo assina na hora; um certificado alterado espera uma reinicialização.** Enviar ao
*criar* um perfil abre o certificado durante o salvamento, então uma senha errada é informada no
formulário, e o perfil já assina de imediato. Enviar pelo formulário de *edição* não abre nada — uma
senha errada em um material substituído aparece na próxima inicialização, como um perfil degradado, na
página em que se faz a correção. [Trocando a origem a quente](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#trocando-a-origem-a-quente) explica por quê.
## O certificado do aprovador
Tudo acima trata do certificado com que **este host** assina. Em um perfil cuja regra de aprovação define
um **conjunto de assinantes** `Approvers` ou `ProfileKeyAndApprovers` (veja [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals)),
entra em cena um segundo tipo de certificado, e nada dele é configurado aqui: cada aprovador **coassina o
arquivo de pagamento com um certificado próprio**, e o produto nunca tem posse dessa chave. Com
`Approvers`, o próprio perfil é **sem chave** — sem certificado, nada aberto na inicialização, e sem
ficar degradado por não ter um.
O aprovador acessa o próprio certificado de uma de duas formas, e o host precisa ter pelo menos uma delas
configurada — um perfil cujo conjunto de assinantes inclui os aprovadores é recusado ao salvar enquanto
nenhuma estiver configurada:
- **No navegador**, pela extensão de navegador **Lacuna Web PKI**, para um certificado no repositório de
certificados do sistema operacional ou em um token criptográfico ou smart card. O host precisa de uma
`WebPki:License` (que não é segredo); cada aprovador instala a extensão uma vez, pela página de
instalação da Lacuna. Uma extensão ausente, desatualizada ou sem suporte é informada no diálogo de
assinatura, com o caminho para instalá-la, antes de qualquer outra coisa acontecer.
- **Em nuvem**, pelo **Lacuna CloudHub**, para um certificado que um provedor guarda no seu HSM (um
*certificado em nuvem*). O host precisa de `CloudHub:ApiKey` e `CloudHub:PublicBaseUrl`; o aprovador
escolhe seu provedor e se autentica lá.
**Qual certificado: e-CPF ou e-CNPJ.** O produto aceita um certificado ICP-Brasil que contenha um CPF e
lê o CPF no campo em que a ICP-Brasil o coloca:
| Certificado | De quem é o CPF que o produto lê | Forma usual |
|-------------|----------------------------------|-------------|
| **e-CPF** — emitido para uma pessoa física | O do titular. | A1 (um arquivo instalado no navegador ou no repositório do sistema operacional) ou A3 (um smart card ou token USB, protegido por PIN). |
| **e-CNPJ** — emitido para uma empresa | O do **responsável**: a pessoa física que o certificado indica como representante da empresa. | Geralmente A3. |
Qualquer um dos dois é aceito. Um certificado que **não contém CPF algum** — um certificado fora da
ICP-Brasil, um certificado de servidor — é recusado com um código próprio
(`approval.certificate-without-cpf`), distinto do de divergência, para que o aprovador saiba o motivo
real. Os certificados podem vir de qualquer AC da lista do ITI, exatamente como o do próprio host.
**O CPF precisa corresponder ao pool.** O pool de aprovadores do perfil registra o CPF de cada aprovador,
e um job congela esse pool quando fica retido. O CPF do certificado precisa ser igual ao **CPF congelado
do membro identificado pela sessão** — a pessoa que entrou pelo link do portal ou pelo Microsoft Entra.
O certificado nunca *escolhe* o membro: um certificado válido de um colega do mesmo pool é recusado na
sua sessão (`approval.certificate-cpf-mismatch`). Por isso, o CPF que o operador digita no pool precisa
ser o CPF do certificado que aquele aprovador vai apresentar — compare-o com o certificado antes de o
primeiro arquivo ficar retido, porque um job já retido mantém o pool que congelou. O seletor de
certificados mostra ao aprovador somente os certificados que contêm aquele CPF congelado, então um CPF
errado no pool aparece como um seletor vazio, que indica, pelos dígitos verificadores, o CPF esperado.
**Ele é verificado por completo, com base no mesmo conjunto de confiança da chave do próprio host.**
Cadeia de certificação, período de validade e revogação são verificados pelo PKI SDK, com base no
[conjunto de confiança](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#certificados-de-teste-e-o-conjunto-de-confiança) ao qual a chave do perfil é
submetida quando assina. Qualquer falha resulta em recusa com `approval.certificate-invalid` e os motivos
do SDK, antes de o token pedir o PIN e antes de existir qualquer assinatura. A verificação não é do tipo
"melhor esforço": trata-se da autorização de um pagamento, e um certificado revogado aprovando um
pagamento é exatamente o caso para o qual a revogação existe. Os motivos vão para o log operacional,
então, se um aprovador for recusado por um motivo que não consegue entender na tela, um operador
consegue.
**O que é registrado.** A aprovação guarda o subject, o emissor, o número de série, o thumbprint SHA-256,
o CPF e — em um e-CNPJ — o CNPJ do certificado, além do nome do provedor em nuvem quando a assinatura
foi feita em nuvem, com o CPF mascarado sempre que o registro é exibido
([Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security#dados-pessoais-dos-aprovadores--cpf-e-e-mail)). O
`GET /api/jobs/{id}/approvals` retorna esses dados no objeto [`certificate`](https://docs.lacunasoftware.com/articles/bulk-signer/rest-api#aprovações) de
cada decisão. O arquivo entregue contém a própria assinatura; o produto não guarda os bytes da
assinatura no registro.
## Trocando a origem a quente
Mudar o certificado de um perfil exige uma reinicialização — o certificado é carregado uma única vez no
boot, e o handle aberto da chave privada nunca é trocado por baixo de um job que pode estar no meio de
uma assinatura. A mudança em si é feita na página do perfil no dashboard, e não em um arquivo de
configuração. Procedimento:
1. Prepare a nova origem (importe o certificado para o repositório de certificados do Windows, copie o novo PFX,
instale o driver PKCS#11, provisione a chave do cofre e seu `.cer`).
2. Na página do perfil, clique em **Editar certificado**, aponte para a nova origem e salve. Um campo de
senha deixado em branco mantém a senha armazenada; digite uma para substituí-la. Um perfil cujo
PKCS#12 foi **enviado** mantém esse arquivo enquanto o seletor fica vazio, recebe um novo quando você
escolhe um, e o perde quando você aponta o perfil para um caminho ou um blob — o formulário informa o
que vai acontecer antes de salvar. Nada é gravado se as coordenadas forem recusadas.
3. Se a nova origem precisa de uma nova variável de ambiente (PIN do PKCS#11, senha de criptografia),
defina-a antes da reinicialização.
4. O perfil passa a exibir um **marcador de reinício pendente**, com os campos que mudaram, e continua
assinando com o certificado anterior até você reiniciar. Esse é o estado honesto, e não um atraso a
contornar: é o que garante que sempre se possa responder *qual certificado assinou este arquivo*.
5. Reinicie o serviço. O banner de inicialização exibe `cert source = …` — verifique se corresponde ao
que você pretendia e se o marcador sumiu. Se o novo certificado não abrir, o perfil sobe **degradado**
com o motivo naquela mesma página; corrija as coordenadas e reinicie de novo.
6. Envie um job de teste pela fila (coloque um arquivo em `input/` ou faça um POST em `/api/files`).
Confira no histórico do job resultante que a nova identidade é a que assinou.
O `Signing:Profiles[]` e o bloco global `Signing:Certificate` são um **seed de uso único** e não têm
efeito em uma implantação que já subiu uma vez, então editá-los não muda nada. Uma implantação cujo banco
ainda está vazio — um primeiro boot ou uma troca de provider de banco de dados — recebe o seed a partir
deles, como antes.
> **Aviso: Mudou na 2.1.0 — certificados são trocados na página do perfil**
>
> Antes da 2.1.0, um certificado era trocado editando o `appsettings.Production.json` e reiniciando. O
> arquivo de configuração agora só é lido para fazer o seed de um banco vazio.
## Diagnóstico de problemas
| Sintoma | Diagnóstico |
|---------|-------------|
| O boot falha com "Signing:PkiSdkLicense is required" | Defina `Signing__PkiSdkLicense` (ambiente) ou `Signing:PkiSdkLicense` (configuração). Veja [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security). |
| Um perfil fica degradado dizendo "PKCS#11 PIN environment variable … is empty" | A variável de ambiente indicada em `PinEnvVar` não está definida para o serviço. Defina-a e reinicie. |
| O boot falha com "WindowsStore source is not supported on this OS" | Você configurou `Source = WindowsStore` no Linux. Troque a origem. |
| Um perfil fica degradado dizendo "does not match Azure Key Vault key … their public keys differ" | `CerPath` (ou o blob) e `KeyName` se referem a pares de chaves diferentes. Verifique-os com o procedimento de OpenSSL / CLI do Azure descrito acima. |
| O boot falha com "Endpoint must be an absolute https:// URL" | O `Endpoint` é apenas o nome DNS do cofre ou usa `http://`. Use a forma completa, por exemplo `https://my-vault.vault.azure.net/`. |
| O boot falha dizendo que tanto um caminho quanto um blob estão configurados | `Path`/`CerPath` e `Blob` são mutuamente exclusivos. Remova um. A mesma recusa dispara quando nenhum dos dois está definido. |
| O boot falha com uma URL de blob rejeitada por conter query string | A URL é uma shared-access signature. SAS não é uma credencial aceita — use `Credential` com `ManagedIdentity`, `ServicePrincipal` ou `AccountKey` e uma URL de blob sem query string. |
| Um perfil fica degradado dizendo que o blob de material de assinatura não existe, ou que sua credencial foi recusada | Verifique se a identidade tem a role **Storage Blob Data Reader** no container e se o blob existe na URL que aparece no banner (nomes de container e de blob diferenciam maiúsculas de minúsculas). Para `AccountKey`, a chave está errada ou foi rotacionada. O host continua em execução; corrija as coordenadas na página do perfil e reinicie. |
| Um perfil fica degradado dizendo que a senha do PKCS#12 não abriu o arquivo | Senha errada no perfil, ou um arquivo danificado. Veja [Verificando se o arquivo é carregável](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#verificando-se-o-arquivo-é-carregável). |
| Um perfil fica degradado dizendo que o PFX está criptografado com PBES2 | Reexporte com o envelope clássico (`openssl pkcs12 -export -legacy`, ou TripleDES-SHA1 no Windows). |
| Um perfil fica degradado dizendo que o arquivo de certificado está vazio (0 bytes) | Forneça o arquivo de novo. |
| Jobs falham com `profile.degraded` | O certificado do perfil não abriu na inicialização, ou seus segredos armazenados não puderam ser decifrados (uma `Signing:ProfileSecretsKey` errada). O motivo está na página do perfil e no histórico do job. Corrija e reinicie — uma nova tentativa do job antes disso falha do mesmo jeito. |
| A assinatura falha com um `403` / `Forbidden` do Azure | O registro de aplicativo não tem a permissão de **sign** na chave. Conceda **Key Vault Crypto User** (RBAC) ou a operação **Sign** (política de acesso). |
| A assinatura falha com um `429` do Azure | Throttling do cofre por excesso de carga. Reduza o `Pipeline:MaxConcurrency` ou solicite um limite maior para o cofre. |
| A assinatura falha imediatamente com "Certificate not found by thumbprint" | O thumbprint não corresponde a nenhum certificado na origem configurada. Confira de novo com os comandos de consulta acima. |
| A assinatura falha com erro de "module load failed" / "C_Initialize" do PKCS#11 | O `.so`/`.dll` do driver não pôde ser carregado — biblioteca do fabricante ausente no host ou não montada no container. |
| A assinatura falha com "Access is denied" ao ler uma chave privada do Windows | A conta virtual do serviço não tem acesso à chave — conceda-o via `certlm.msc → Gerenciar Chaves Privadas`. |
| PDF assinado rejeitado por um verificador externo | Confira se a versão da política está atualizada — os arquivos de política da ADR-Básica são versionados pelo ITI. Os verificadores externos precisam aceitar a versão que o Bulk Signer emite. |
| Um **certificado de teste Turing / Fermat** é recusado na imagem publicada | O conjunto de confiança da build de release é só a ICP-Brasil — veja [Certificados de teste e o conjunto de confiança](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#certificados-de-teste-e-o-conjunto-de-confiança). Em um host de homologação, defina `Signing:TrustLacunaTestRoot = true` **e** `ASPNETCORE_ENVIRONMENT=Staging`; a chave sozinha é recusada em `Production`. |
| O boot falha com "Signing:TrustLacunaTestRoot is true while the environment is 'Production'" | Comportamento intencional. Remova a chave em um host de produção; em um host de homologação, defina o nome do ambiente como `Staging`. |
| O **Assinar e aprovar** de todo aprovador é recusado como `approval.certificate-invalid` | Geralmente o problema é a implantação, e não os certificados: o host não consegue verificar a revogação (não tem acesso de saída às LCRs das ACs), ou os certificados são de teste e o conjunto de confiança é o de produção. Os motivos estão no log operacional. Veja [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals#diagnóstico-de-problemas). |
| Um aprovador é sempre recusado como `approval.certificate-cpf-mismatch` | O CPF do pool e o do certificado diferem — veja [O certificado do aprovador](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#o-certificado-do-aprovador). Corrija o CPF no pool de aprovadores do perfil; um job já retido mantém o pool que congelou. |
Veja [Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting) para o catálogo completo de falhas.
---
**A seguir:** [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security) — tratamento de segredos e o modelo de ameaças.
**Anterior:** [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration).
# [Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security)
O modelo de segurança do Lacuna Bulk Signer voltado ao operador — como os segredos são armazenados,
como a autenticação funciona e o que o serviço faz para evitar divulgação acidental.
## O modelo de ameaças em um parágrafo
O Bulk Signer é um serviço *on-premises* que guarda quatro tipos de segredo: a **licença do PKI SDK**,
**material de certificado, PINs e credenciais de nuvem**, a **senha de criptografia** (quando a
criptografia está habilitada) e a **chave de API**. Ele expõe uma API REST e um dashboard web, ambos
protegidos por essa única chave de API, com uma sessão baseada em cookie para operadores. Não há
atualização automática. O modelo de ameaças parte do pressuposto de que o serviço é executado em um host
confiável dentro de uma rede confiável, com o TLS terminado em um proxy reverso.
Uma implantação que habilita a [etapa de aprovação](https://docs.lacunasoftware.com/articles/bulk-signer/approvals) também guarda **dados pessoais
sobre seus próprios aprovadores** (nome, e-mail, CPF) e, enquanto um job de pagamento está em
andamento, sobre cada beneficiário do arquivo.
Desde a 2.1.0, os perfis de assinatura ficam no banco de dados operacional; então, em uma implantação
que faz upload de material de certificado pelo dashboard, o banco também pode guardar **uma chave
privada** — criptografada com uma chave mantida fora dele. Veja
[a chave de segredos dos perfis de assinatura](https://docs.lacunasoftware.com/articles/bulk-signer/security#a-chave-de-segredos-dos-perfis-de-assinatura-signingprofilesecretskey).
Uma instalação padrão não faz **nenhuma conexão de saída**. Toda funcionalidade opcional que muda isso
vem desligada a menos que você a habilite:
| Funcionalidade | Dependência de saída |
|----------------|----------------------|
| `Signing:Certificate:Source = AzureKeyVault` | `*.vault.azure.net` + `login.microsoftonline.com` — uma chamada de assinatura por assinatura. Veja [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#origem--azurekeyvault). |
| `Signing:Certificate:…:Blob` | `*.blob.core.windows.net` — uma leitura no boot. Veja [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#lendo-o-arquivo-de-um-blob). |
| `Signing:Profiles[].Method = LacunaSigner` | Seu tenant do Lacuna Signer. Veja [Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer). |
| `Storage:Provider = AzureFiles` | `*.file.core.windows.net` — cada staging, promoção e realocação. |
| `Database:Provider = SqlServer` | Sua instância de SQL Server ou Azure SQL. |
| `Auth:EntraId` | `login.microsoftonline.com` — somente para o login interativo. |
| `CloudHub:ApiKey` | Lacuna CloudHub (`CloudHub:Endpoint`, por padrão a instância pública da Lacuna) — somente quando um aprovador assina com um certificado em nuvem. Nada o consulta no boot nem na verificação de prontidão. |
| `Branding:CustomerLogo:Blob` | `*.blob.core.windows.net` — uma leitura no boot. |
| `Telemetry:Enabled = true` | Azure Application Insights. Veja [Telemetria](https://docs.lacunasoftware.com/articles/bulk-signer/telemetry). |
## Autenticação
Dois esquemas de autenticação compartilham uma política de autorização:
- **Header `X-API-Key`.** Clientes programáticos enviam a `Auth:ApiKey` configurada no header
definido por `Auth:ApiKeyHeader` (padrão `X-API-Key`). O handler compara os valores em tempo
constante, para evitar ataques de temporização (timing oracles).
- **Cookie.** Operadores colam a mesma chave de API em `/login`; o endpoint de login a troca por um
cookie (`Auth:CookieName`, padrão `lbs-auth`) com `SameSite=Strict` + `HttpOnly`. As requisições
subsequentes do dashboard carregam o cookie.
Os dois esquemas atendem à mesma política de autorização em todo endpoint protegido. `/api/health`,
`/api/ready`, `/login`, `/api/auth/login`, `/api/auth/entra-login`, `/api/auth/logout`,
`/access-denied`, `/api/culture` e `/branding/customer-logo` são anônimos, assim como as superfícies de
aprovação descritas [abaixo](https://docs.lacunasoftware.com/articles/bulk-signer/security#a-página-de-aprovação-por-job-não-é-autenticada) e o
`/api/signer/notifications`, que é anônimo para os dois esquemas do produto e autenticado
[pelo próprio segredo](https://docs.lacunasoftware.com/articles/bulk-signer/security#notificações-do-lacuna-signer-signerwebhooksecret). Todo endpoint declara
explicitamente seu nível de acesso, então nenhuma rota fica pública por acidente.
O `/branding/customer-logo` entrega o logotipo do cliente que as páginas de login e do aprovador mostram
antes de alguém se autenticar: uma imagem que esta instância leu no boot, e nada sobre nenhum job. Um
logotipo SVG é entregue com `Content-Security-Policy: default-src 'none'`, de modo que um script contido
no arquivo não pode ser executado nem quando alguém abre a URL diretamente. Sem logotipo carregado,
a rota responde `404` (`branding.customer-logo-not-available`).
### Prontidão: o veredito é anônimo, o diagnóstico não
> **Aviso: Mudou na 2.6.0 — o /api/ready não traz mais detail**
>
> O probe anônimo trazia, para cada verificação, um texto escrito para o operador que fosse corrigi-la: o
> host e o catálogo do SQL Server, o caminho ou a URL do compartilhamento de cada pasta de entrada, a
> mensagem de falha do próprio SDK de armazenamento, o caminho ou o endpoint do cofre de um certificado
> degradado. Nada disso era uma credencial; em conjunto, formava um mapa da implantação, legível por
> qualquer um que alcançasse a porta. Um monitor que interpretava o `detail` passa a usar o
> `/api/ready/details` e acrescenta o header da chave de API.
- **`GET /api/ready`** responde apenas com o veredito: `ready`, e o `name` e o `ok` de cada
verificação. O campo `detail` está ausente, e não nulo, então um orquestrador que lê o código de
status não é afetado, e quem o observa ainda vê *qual* linha ficou vermelha.
- **`GET /api/ready/details`** traz o mesmo relatório com o detalhe de cada verificação, protegido pela
chave de API ou por uma sessão de operador, com a mesma regra de 200 / 503.
- **`Readiness:RequireApiKey`** (padrão `false`) protege o próprio `/api/ready` com a mesma política,
para um host cujo probe consegue enviar o `X-API-Key` ou que nenhum probe consulta. A opção vem
desligada por padrão, diferentemente do `Metrics:RequireApiKey`, porque o health check do Azure App
Service não consegue enviar a chave e interpreta um `401` como não saudável. Ela muda quem pode
consultar, nunca o que é respondido; as linhas `ready` e `metrics` do banner de resumo de prontidão
mostram `(anonymous)` ou `(API key)`, de modo que uma chave que não foi vinculada fica visível no boot.
- Como o corpo anônimo era, para quem quer que o consultasse, o registro de uma falha transitória, uma
**mudança** de veredito de uma verificação é registrada no log durável uma vez por mudança — `went red`
com o detalhe em Warning, `recovered` em Information —, nunca a cada consulta.
Nada em nenhuma das duas rotas foi escrito para o público externo. Mantenha o probe fora de qualquer rede
que não seja a de monitoramento, esteja ele protegido pelo `Readiness:RequireApiKey` ou não.
### O dashboard é cercado duas vezes: no endpoint e dentro do circuito
> **Observação: Corrigido na 2.2.1**
>
> Antes da 2.2.1, um navegador anônimo conseguia alcançar as páginas de operador navegando *dentro* do
> dashboard, onde a autorização do endpoint nunca era consultada. Atualize toda implantação 2.0.x ou 2.1.x
> cujo dashboard seja alcançável por pessoas que não são operadores.
A política no endpoint de uma página é o que transforma um `GET /backup` direto em um redirecionamento
para `/login`. Ela não é o que protege a página depois que um navegador mantém uma conexão ativa com o
dashboard: toda página é interativa, e uma navegação interna — um clique em um link, ou uma entrada de histórico
inserida pelo console do navegador — é resolvida dentro dessa conexão *sem uma requisição HTTP*, então
o endpoint nunca é consultado. Três mecanismos sustentam a segunda cerca, e cada um é necessário:
- **O roteador respeita a política da página.** Toda navegação é autorizada com base na identidade da
conexão. Uma recusa recarrega a página por HTTP, de modo que o servidor responde exatamente como a uma
requisição direta — um desafio para `/login` ou, com o Entra ligado, uma proibição para
`/access-denied` ou para o portal. A recusa é registrada no log em Warning, com o caminho e os nomes dos
esquemas, e nada mais.
- **A conexão sabe quem você é.** Ela carrega a sessão do navegador — a do operador, a do aprovador ou a
do Entra — e as páginas decidem.
- **Estar autenticado não é ser operador.** Com o Entra desligado, cada política de operador verifica qual
esquema emitiu a identidade, de modo que a sessão de portal de um aprovador — legitimamente admitida em
`/jobs/{id}` — não satisfaz as páginas de operador só por estar autenticada. Com o Entra ligado, as
verificações de role já faziam isso.
A gaveta de navegação e o botão que a abre são renderizados apenas para operadores, então um aprovador na
única página que os dois públicos compartilham não recebe destinos de operador. O *Sair* — o do menu de
conta, e o do cabeçalho do portal do aprovador — encerra qualquer sessão de navegador presente, inclusive
a sessão de link do aprovador. Uma sessão de link é levada para `/approvals/link-required`, que indica
o caminho de volta; qualquer outra sessão vai para `/login`.
Há uma ressalva deliberada, que vale conhecer: a identidade é capturada quando a conexão do dashboard é
aberta e não é revalidada enquanto ela permanece aberta; então, uma sessão que expira no meio da conexão
mantém a página em que está até o próximo carregamento completo. A próxima requisição HTTP, e toda
chamada REST, percebem a expiração de imediato.
### Modo de login pelo Microsoft Entra ID (opcional)
Quando o [`Auth:EntraId`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#authentraid--login-opcional-pelo-microsoft-entra-id) está
configurado, o comportamento no navegador muda, e o da automação não:
- **O login legado por chave de API fica desligado, não relegado a segundo plano.** O `/login` renderiza
o login da Microsoft; um POST feito à mão em `/api/auth/login` é recusado mesmo com uma chave correta;
e a política de operador deixa de aceitar sessões com cookie legado, de modo que ligar o modo
**encerra de uma vez toda sessão de navegador criada por chave de API**, em vez de deixá-las valendo
por mais oito horas. Planeje a transição como uma desconexão geral. O `X-API-Key` para chamadores REST
não é tocado — automação não consegue fazer um login interativo.
- **O acesso é decidido por app roles, somente pela claim de roles.** `Administrator` é o operador;
`Approver` abre as superfícies de aprovação, onde o pool congelado continua delimitando quais jobs a
pessoa vê. Uma conta autenticada sem role — e um Approver cuja conta não tem claim de e-mail, já
que os pools são vinculados por e-mail — é recusada em `/access-denied`. **Sem mapeamento por grupo de
segurança:** uma edição de grupo no tenant nunca pode ser uma mudança de autorização invisível.
- **Uma sessão pode carregar as duas roles**, e a segregação de funções é sustentada pelas verificações
de role: uma sessão só de Administrator não satisfaz nenhuma política de aprovador, e vice-versa.
- **As sessões são cookies com expiração deslizante de 8 horas, em um esquema próprio** (`SameSite=Lax`, porque o
login retorna por um redirecionamento entre sites vindo do tenant; `HttpOnly`). Sair é apenas local —
limpa a sessão do Bulk Signer e deliberadamente não encerra a sessão Microsoft da pessoa, de modo que
um novo login imediato funciona silenciosamente. Isso é comportamento normal de SSO, não um defeito.
- **Hardening recomendado no tenant:** defina **Atribuição necessária** no aplicativo empresarial,
para que contas não atribuídas sejam barradas já na porta da Microsoft. A aplicação exige a role de
qualquer forma — depender apenas da configuração do tenant transformaria uma simples opção no tenant em
uma forma de contornar a autorização.
Passo a passo: [Instalação](https://docs.lacunasoftware.com/articles/bulk-signer/installation#login-pelo-microsoft-entra-id-opcional).
#### `Auth:EntraId:ClientSecret`
O modo faz do host um **cliente OIDC confidencial**, e a credencial para isso é o client secret do
registro de aplicativo. Ele segue as mesmas regras do `AppSecret` do Key Vault abaixo: permitido na
configuração, variável de ambiente recomendada.
| Onde ele pode ficar | Permitido? |
|---------------------|------------|
| `appsettings.json` (versionado) | Tecnicamente funciona — **nunca faça isso** |
| `appsettings.Production.json` (ignorado pelo Git) | Sim |
| `Auth__EntraId__ClientSecret` | Sim — **recomendado** |
O valor do segredo para um atacante é limitado: ele autentica a *aplicação*, não um usuário. Tê-lo em
mãos, por si só, não autentica ninguém nem concede nenhuma das app roles. Faça a rotação dele no tenant
periodicamente; um segredo expirado faz falhar o handshake OIDC, e não o boot.
Uma seção `Auth:EntraId` preenchida pela metade é recusada no boot, com um erro que indica a chave
ausente.
Um modo de autenticação parcialmente configurado é uma porta cuja fechadura ninguém terminou de
instalar.
### Rotação da chave de API
A chave de API é estática. Para rotacioná-la:
| Alvo | Passos |
|------|--------|
| Linux | Edite `Auth__ApiKey=` em `/etc/bulksigner/bulksigner.env`, então `sudo systemctl restart bulksigner`. |
| Windows | `[Environment]::SetEnvironmentVariable("Auth__ApiKey", "", "Machine")`, então `Restart-Service LacunaBulkSigner`. |
| Docker | Edite `Auth__ApiKey=` em `deploy/docker/.env`, então `docker compose up -d` (recria o container). |
A chave precisa ter ao menos 16 caracteres; o serviço se recusa a iniciar com um valor mais curto — e sem
valor algum: desde a 2.3.1, o `appsettings.json` distribuído não traz chave de exemplo, então uma
implantação que nunca definiu `Auth:ApiKey` se recusa a iniciar, indicando essa chave. Use uma string
aleatória gerada por um CSPRNG — por exemplo `openssl rand -base64 32` no Linux/Mac, ou no PowerShell:
```powershell
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 } | ForEach-Object { [byte]$_ }))
```
> **Aviso**
>
> A rotação causa interrupção: todo cookie de operador existente e todo cliente programático começam a
> falhar já na próxima requisição. Agende-a durante uma janela de manutenção, ou use um breve
> período de sobreposição em que duas chaves conhecidas sejam aceitas por um filtro de proxy reverso (o
> próprio Bulk Signer aceita exatamente uma chave).
### Notificações do Lacuna Signer (`Signer:WebhookSecret`)
O `POST /api/signer/notifications` é onde o webhook do Lacuna Signer faz as entregas, para perfis
[`SignerFolder`](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#assinando-em-uma-pasta-do-lacuna-signer--signerfolder) (2.16.0; passo a
passo para o operador em [Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#notificações--o-webhook)). O
Signer não tem nem um cookie de operador nem a chave de API deste host; por isso, a rota é anônima para os
dois esquemas e autentica a entrega por conta própria: o header `Authorization` precisa ser
`Bearer `, comparado em tempo constante, para que nem o conteúdo nem o tamanho de um
palpite errado sejam mensuráveis. Ele **não** é, de propósito, o `X-API-Key` — esse header é a credencial
REST do produto, e uma chave de operador nele não serve para nada nesta rota.
| Onde o `Signer:WebhookSecret` pode ficar | Permitido? |
|------------------------------------------|------------|
| `appsettings.json` (versionado) | Tecnicamente funciona — **nunca faça isso** |
| `appsettings.Production.json` (não versionado) | Sim |
| Variável de ambiente `Signer__WebhookSecret` | Sim — **recomendado** |
O que o segredo vale para um atacante é limitado pelo desenho: **uma notificação é uma campainha, não uma
ordem.** Nada no corpo dela é levado em conta — ela cita documentos e pastas, este host enfileira uma
checagem para cada documento na pasta vinculada a um perfil habilitado, e a checagem relê o documento no
Signer com a própria chave de API deste host antes de qualquer decisão. Uma entrega forjada pode, no
máximo, fazer este host perguntar ao Signer sobre um documento; ela não consegue fazê-lo assinar um que
não seja a vez atual do titular do certificado, exatamente na pasta vinculada, e não descobre nada sobre
nenhum job — uma resposta de sucesso não tem corpo. A diferença entre `202` e `204` revela, a quem tem o
segredo, se um id de pasta citado está vinculado a um perfil habilitado. A rota tem a própria cota de
requisições (`RateLimiting:SignerNotification`). O segredo precisa ter pelo menos 32 caracteres e é
removido dos logs, tanto como valor literal quanto como propriedade estruturada. Sem ele, a rota responde
`404` e só a varredura de pastas encontra documentos.
**Para fazer a rotação**, defina o novo valor no host e no webhook do Signer ao mesmo tempo; uma entrega
feita no intervalo é recusada com `401`, que o Signer tenta de novo, e a varredura encontra o que uma nova
tentativa deixar para trás.
### Tempo de vida da sessão em cookie
Os cookies são emitidos com `HttpOnly` e `SameSite=Strict`, e marcados como `Secure` quando a
requisição foi HTTPS. O ticket de autenticação tem uma **expiração deslizante de 8 horas** — toda
requisição autenticada zera o relógio; depois de oito horas de inatividade, o operador é desconectado.
Não existe uma opção "lembrar de mim" de maior duração. Operadores podem sair explicitamente pelo menu de conta no
dashboard.
### O key ring de sessão e onde ele fica
Ambos os cookies de sessão — o do operador e o do aprovador — são payloads do ASP.NET Data Protection,
então o key ring que os protege decide quem consegue validar um cookie. **Sua localização segue o
`Cluster:Enabled` em vez de uma configuração própria**, deliberadamente: uma implantação capaz de
escolher o local independentemente da topologia é uma implantação capaz de escolher a combinação que não
funciona.
| `Cluster:Enabled` | Onde o ring fica | Em repouso |
|---|---|---|
| `false` (padrão) | `keys/` em `Storage:Root` | Criptografado com DPAPI no Windows; sem criptografia no Linux |
| `true` | Linhas no banco operacional | **Texto claro**, protegido pelo controle de acesso do próprio banco de dados |
Há duas consequências do modo cluster, e a segunda é fácil de deixar passar:
- **Com a chave ligada, o criptografador DPAPI do Windows é descartado.** O DPAPI com escopo de máquina é
exatamente a propriedade que torna uma cópia de `keys/` inútil em outro host — e exatamente a
propriedade que torna um ring ilegível para uma irmã, de modo que mantê-lo seria manter o defeito. No
Windows, isso enfraquece a proteção em repouso. Não custa nada na topologia suportada, cujo container
Linux também não tem criptografia em repouso para o ring em disco, e é o único ponto em que ligar o modo
cluster abre mão de um controle em vez de acrescentar um.
- **Um banco inacessível faz a requisição falhar, sem fallback.** Um host que silenciosamente criasse
sessões a partir de um ring por instância emitiria cookies que suas irmãs rejeitam — a desconexão
intermitente que o ring compartilhado existe para remover.
De qualquer forma, **acesso de leitura ao ring equivale a uma sessão como qualquer pessoa**: trate `keys/` com as
mesmas ACLs do banco de dados, e trate a connection string como a credencial que ela é. Veja
[a connection string do banco operacional](https://docs.lacunasoftware.com/articles/bulk-signer/security#a-connection-string-do-banco-operacional) e
[Alta disponibilidade](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability#o-key-ring-de-sessão-fica-em-texto-claro-no-banco).
## Armazenamento da licença
A licença do Lacuna PKI SDK é uma string base64. Há duas formas de carregá-la:
| Onde | Persiste após | Preferido? |
|------|---------------|------------|
| `Signing:PkiSdkLicense` em `appsettings.Production.json` | Reinício do serviço | Aceitável se o arquivo for ignorado pelo Git e o local de instalação tiver ACL restrita à conta de serviço |
| Variável de ambiente `Signing__PkiSdkLicense` | Reinício do serviço | **Sim** — mantém a licença literal fora da árvore de arquivos |
A variável de ambiente tem precedência no boot. Configuração por alvo:
- **Linux:** `/etc/bulksigner/bulksigner.env` (modo `0640`, dono `bulksigner`).
- **Windows:** variável de ambiente de escopo de máquina definida pelo `Install-Service.ps1`.
- **Docker:** `deploy/docker/.env`.
### A licença do Web PKI não é um segredo (`WebPki:License`)
A única licença do produto que **não** é tratada como segredo, e deliberadamente. O Lacuna Web PKI é
executado no navegador de um aprovador quando ele assina uma aprovação, e a licença é enviada para lá em
texto claro:
ela é vinculada aos domínios da implantação, não a um portador, e possuí-la não permite a ninguém
assinar nada. Por isso, ela não passa por nenhuma das camadas de mascaramento, pode ficar no
`appsettings.Production.json` ao lado das chaves que não são segredo, e nenhuma superfície a mascara. O
banner de resumo de prontidão e a página **Sistema** informam se ela está configurada ou não. As ACLs de
arquivo desta página continuam valendo a pena para ela, mas pela **integridade**, e não pela
confidencialidade: uma licença trocada pela de outra implantação mandaria os aprovadores para uma página
cujo Web PKI se recusa a funcionar. A chave em si: [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration).
## Segredos das origens de certificado
> **Aviso: Mudou na 2.1.0 — a configuração de certificado é um seed**
>
> Os perfis de assinatura, e o certificado com que cada um assina, agora ficam no banco operacional.
> `Signing:Certificate` e `Signing:Profiles[]` são lidos **uma vez**, no primeiro boot com a tabela de
> perfis vazia, e ignorados depois — são um seed (carga inicial); a partir daí, um certificado — e sua
> senha ou segredo de aplicação — é informado na página do perfil no dashboard e armazenado
> **criptografado** com a
> [`Signing:ProfileSecretsKey`](https://docs.lacunasoftware.com/articles/bulk-signer/security#a-chave-de-segredos-dos-perfis-de-assinatura-signingprofilesecretskey).
> As regras abaixo continuam regendo o que o seed pode conter, e toda chave com segredo nela continua
> registrada no mascaramento de logs, de modo que a senha que sobrou de uma implantação antiga continua
> sendo mascarada depois que deixa de ser usada.
### Senha do PFX
Senhas de PFX se comportam como outros segredos de configuração — definidas em
`Signing:Certificate:Pfx:Password`, ou sobrescritas via
`Signing__Certificate__Pfx__Password`. O arquivo PFX em si fica no caminho de
`Signing:Certificate:Pfx:Path`; proteja-o com ACLs de arquivo restritivas.
### PIN do PKCS#11 — somente por variável de ambiente
Por design, o PIN do PKCS#11 **nunca é aceito em arquivos de configuração**. O validador impede a
inicialização se uma chave `Pin` literal aparecer em `Signing:Certificate:Pkcs11`. A mesma regra se aplica
dentro de cada entrada de `Signing:Profiles[]`, e aos dados de perfil armazenados. O PIN é lido em tempo
de execução da variável de ambiente indicada em `Signing:Certificate:Pkcs11:PinEnvVar` (padrão
`BULK_SIGNER_PKCS11_PIN`), e vários perfis podem compartilhar a mesma variável de ambiente ou definir
variáveis distintas via `PinEnvVar` por perfil. A página do perfil no dashboard mostra o *nome* dessa
variável, nunca um valor.
Esta é a mais estrita das regras de tratamento de segredos:
| Onde o PIN pode ficar | Permitido? |
|-----------------------|------------|
| `appsettings.json` (versionado) | Não |
| `appsettings.Production.json` (ignorado pelo Git) | Não — o validador impede o boot |
| Variável de ambiente | Sim (o único caminho) |
### Credenciais do Azure Key Vault
O `Signing:Certificate:AzureKeyVault:AppSecret` é um client secret do Microsoft Entra ID.
Diferentemente do PIN do PKCS#11, ele **é** permitido em um arquivo de configuração — o validador não o
recusa — mas a forma por variável de ambiente é recomendada:
```bash
export Signing__Certificate__AzureKeyVault__AppSecret='…'
```
| Onde o client secret pode ficar | Permitido? |
|---------------------------------|------------|
| `appsettings.json` (versionado) | Nunca — ele acabaria no controle de versão |
| `appsettings.Production.json` (ignorado pelo Git) | Sim, e o validador o permite |
| Variável de ambiente | Sim — **preferido** |
O que esta origem *remove* do host é o ponto mais importante: não há chave privada em disco, logo não há
arquivo PFX para aplicar ACL e nenhum material de chave em um backup. O que ela *acrescenta* é uma
credencial de nuvem rotacionável. Faça a rotação dela no Azure (crie um novo client secret, atualize a
variável de ambiente, reinicie e então apague o segredo antigo no Azure) — a credencial é muito mais fácil de
rotacionar que um certificado, então prefira uma expiração curta.
O arquivo `.cer` em `CerPath` **não** é um segredo. Ele contém apenas material público; proteja sua
integridade, não sua confidencialidade.
O client secret é registrado nas duas camadas de mascaramento descritas abaixo, de modo que é removido
dos logs duráveis quer apareça como propriedade estruturada, quer interpolado em uma mensagem de
exceção.
### Credenciais do blob de material de assinatura
Opcionais e ausentes em toda implantação que mantém os arquivos de certificado no host. O
[`Pfx:Blob` e o `AzureKeyVault:Blob`](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#lendo-o-arquivo-de-um-blob) permitem que essas
duas origens leiam seu arquivo do Azure Blob Storage. A credencial é escolhida por bloco entre os mesmos
três modos do provider de armazenamento abaixo, e seu `AppSecret` / `AccountKey` seguem exatamente as
regras de `AppSecret` acima.
Dois aspectos dela são decisões de segurança, e não detalhes de configuração.
**A credencial é separada da do cofre, mesmo quando é a mesma aplicação.** O
`AzureKeyVault:AppSecret` autoriza *uso de uma chave*; o `AzureKeyVault:Blob:AppSecret` autoriza *leitura
de um blob*. Nada é herdado: o bloco repete `TenantId` / `AppId` / `AppSecret` mesmo quando eles
se referem à mesma aplicação do Entra. Duas credenciais que concedem coisas diferentes são configuradas
separadamente, e uma rotação esquecida faz o boot falhar de forma explícita, em vez de seguir funcionando
com apenas uma delas.
**O que uma chave de conta custa depende do que o blob abriga.**
| Blob em | O que ele abriga | O que uma `AccountKey` vazada entrega |
|----------|------------------|----------------------------------------|
| `AzureKeyVault:Blob` | o `.cer` — material público | um certificado público; a chave privada permanece no cofre |
| `Pfx:Blob` | o arquivo PKCS#12 | **a chave de assinatura** |
Uma credencial baseada em token precisa apenas de **Storage Blob Data Reader** no container. Nada no
Bulk Signer grava, lista, move ou faz lease de um blob, então nunca é necessário nada mais amplo.
### Repositório de certificados do Windows
Nenhum segredo na configuração — a seleção é feita pelo local do repositório, pelo nome do repositório
e pelo thumbprint SHA-1. O certificado em si foi importado com a proteção que o sistema operacional
oferecia no momento da importação. Use `LocalMachine` quando a conta virtual do serviço precisar acessar
a chave, e conceda à conta virtual acesso à chave privada via `certlm.msc` → certificado → Todas as
Tarefas → Gerenciar Chaves Privadas.
### A raiz de teste da Lacuna, e por que a produção a recusa (`Signing:TrustLacunaTestRoot`)
Um build de release confia nas raízes da ICP-Brasil. O `Signing:TrustLacunaTestRoot = true`
(acrescentado na 2.3.0, desligado por padrão) amplia esse conjunto de confiança com a **raiz da PKI de
teste da Lacuna** — a emissora dos certificados de teste Turing / Fermat — mais o conjunto de confiança
do Windows do PKI SDK, para que uma homologação possa executar a imagem publicada com certificados de
teste em vez de um e-CPF real por aprovador. Três pontos importantes:
- **Ele é recusado no boot quando o `ASPNETCORE_ENVIRONMENT` é `Production`.** Portanto, um host de
produção que herdou a configuração se recusa a iniciar, indicando a chave, em vez de confiar em uma raiz
que qualquer um pode baixar. Em um host de homologação, defina o nome do ambiente como `Staging` (ou
qualquer outro nome).
- **É um único conjunto de confiança para o host inteiro.** A chave de cada perfil no momento da
assinatura, cada verificação posterior e o certificado de cada aprovador são validados com base nas
mesmas raízes.
- **Ele é bem visível.** A linha `trust set` do banner de resumo de prontidão o indica, e o console e o
log durável exibem um aviso a cada boot. A chave não é um segredo.
Veja [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates) para a descrição completa.
## Credenciais de armazenamento do Azure Files
Opcionais e ausentes em toda implantação que mantém o armazenamento local. Quando
`Storage:Provider` — ou qualquer `Storage:Inputs[].Provider` — é `AzureFiles`, o host guarda uma
credencial capaz de ler e gravar nos compartilhamentos configurados. Os três modos não são
equivalentes:
| Modo | Segredo guardado pelo host | Raio de impacto se o host for comprometido |
|------|--------------------------|---------------------------------------------|
| `ManagedIdentity` | **Nenhum** | As próprias atribuições de role da identidade, e nada portátil — não há valor a roubar e reproduzir em outro lugar |
| `ServicePrincipal` | `AppSecret` | As atribuições de role do registro de aplicativo, até o segredo ser rotacionado |
| `AccountKey` | `AccountKey` | **A conta de armazenamento inteira** — todo compartilhamento nela, leitura, escrita e exclusão, sem expiração e sem forma de restringir |
**Prefira `ManagedIdentity` sempre que o host for executado dentro do Azure.** É o único modo sem
nenhum segredo. Ele é **somente atribuído pelo sistema**, e deliberadamente não é `DefaultAzureCredential`, então
nunca recai para a identidade do `az login` de um desenvolvedor e não pode parecer funcionar em um laptop
enquanto está ausente em produção.
Ambos os modos com token autenticam por OAuth, que para o Azure Files precisa de uma das roles de **dados
de arquivo privilegiados**: conceda `Storage File Data Privileged Contributor`, delimitada ao
compartilhamento, e não à conta. O menor privilégio tem um limite mínimo que vale mencionar: uma role
somente leitura **não** basta nem para uma pasta de entrada, já que o pipeline faz lease do arquivo de
entrada enquanto o coloca em staging e o apaga após a verificação.
> **Aviso: Uma armadilha ao restringir a atribuição a um único compartilhamento**
>
> A string de escopo do plano de dados é:
>
> ```text
> /subscriptions//resourceGroups//providers/Microsoft.Storage/storageAccounts//fileServices/default/fileshares/
> ```
>
> `fileshares`, numa só palavra e em minúsculas. Uma atribuição criada com a grafia do plano de
> gerenciamento, `shares`, é aceita sem reclamação e depois não concede nada — e falha como
> `AuthorizationPermissionMismatch` na primeira chamada, o que parece um erro de role, e não de escopo.
> Confira a string de escopo antes de trocar qualquer credencial.
O `AccountKey` existe para hosts que não conseguem alcançar o tenant de forma alguma. É o único modo
sobre o qual o host **avisa na inicialização**, no console e no log durável. Coloque-o na variável de
ambiente, e não no arquivo de configuração, e faça a rotação dele na mesma periodicidade de qualquer outra
credencial com escopo de conta.
Ambos os segredos são registrados nas duas camadas de mascaramento — a partir do bloco de nível superior
*e* de cada override por pasta, de modo que uma credencial deixada para trás por uma pasta que voltou
para armazenamento local ainda é mascarada. Um bloco de credencial incompleto impede o boot, com um erro
que indica a chave ausente.
### Nenhum artefato assinado é jamais alcançável por URL
Nenhuma URL de SAS (shared access signature) é gerada para um artefato assinado, qualquer que seja o
provider que abrigue `output/`. Os downloads são transmitidos pela própria aplicação, então o
`GET /api/jobs/{id}/output` — e o arquivo compactado de saídas assinadas de vários jobs,
`GET /api/jobs/archive` — têm o mesmo formato de resposta, a mesma autorização e os mesmos códigos de
problema em um compartilhamento e em disco local.
## A connection string do banco operacional
Com `Database:Provider = Sqlite` — o padrão —, o `ConnectionStrings:Default` indica um arquivo e não
contém credencial; a proteção é a ACL de arquivo em `db/`,
[abaixo](https://docs.lacunasoftware.com/articles/bulk-signer/security#db-merece-o-mesmo-cuidado-que-keys-e-o-produto-não-o-configura-para-você). Com `SqlServer`, a
mesma chave passa a ser **a credencial inteira**.
**Prefira um formato sem segredo.** Os três não são equivalentes:
| Formato | Segredo guardado pelo host | Raio de impacto se o host for comprometido |
|-------|--------------------------|---------------------------------------------|
| Managed identity (`Authentication=Active Directory Managed Identity`) | **Nenhum** | As próprias permissões da identidade no banco de dados, e nada portátil |
| Autenticação integrada do Windows (`Integrated Security=True`) | **Nenhum** | As permissões concedidas a esse principal, e somente a partir de um host ingressado no domínio que consiga obter um ticket |
| Login SQL, ou service principal do Entra (`User ID` + `Password`) | A senha | O banco de dados, de qualquer lugar que alcance o servidor, até a senha ser rotacionada |
As recusas de boot citam o data source e nunca a string, e a chave é registrada nas duas camadas de
mascaramento. O banner de resumo de prontidão, o console e o `/api/ready/details` identificam o banco
por engine, servidor e nome do banco de dados — nunca citando a connection string.
**Criptografe a conexão.** O `Encrypt` tem padrão `True` no cliente SQL, que é o que você quer. O
`TrustServerCertificate=True` mantém a criptografia e abandona a verificação de identidade, então
reabre a brecha para man-in-the-middle que ela fechava; use-o conscientemente, em um segmento confiável, e
prefira instalar um certificado em que o host confie. O `Encrypt=False` não deve aparecer em uma string
de produção.
## Senha de criptografia
Quando `Encryption:Enabled = true`, a senha de criptografia deriva a chave AES-256-GCM na inicialização
via PBKDF2-HMAC-SHA256. Diferentemente do PIN do PKCS#11, a senha **é** permitida na configuração (a
chave `Encryption:Password`) — espera-se que os operadores a coloquem no
`appsettings.Production.json`, que é ignorado pelo Git. A variável de ambiente
`BULK_SIGNER_ENCRYPTION_PASSWORD` (ou o nome configurado por `Encryption:PasswordEnvVar`) é o override
preferido e tem precedência no boot.
O validador não impede que a senha seja versionada no `appsettings.json` não criptografado, mas esse é
o local errado — mantenha-a no `appsettings.Production.json` ou na variável de ambiente.
A chave derivada fica apenas na memória do processo — nunca é gravada em disco, nunca é registrada em log
e nunca é retornada por nenhum endpoint. Veja [Criptografia](https://docs.lacunasoftware.com/articles/bulk-signer/encryption) para os detalhes do algoritmo e o
envelope em disco — inclusive por que, com a criptografia ligada, a senha é também o único caminho de
volta a um arquivo que um aprovador rejeitou.
## A chave de segredos dos perfis de assinatura (`Signing:ProfileSecretsKey`)
**Uma implantação cujos perfis têm um segredo precisa dela.** Os perfis de assinatura são linhas no
banco operacional, e os segredos que eles contêm são criptografados com uma chave derivada desta. Uma
implantação cujos perfis não têm nenhum segredo — um PFX sem senha, um token PKCS#11 cujo PIN é uma
variável de ambiente, um certificado do repositório do Windows — não precisa de chave, e ela nunca é
exigida.
Cinco valores no banco são criptografados com ela: uma **senha de PKCS#12**, um **segredo de aplicação do
Azure Key Vault**, **bytes de PKCS#12 enviados por upload** e, de um blob de material de assinatura, o **segredo do
service principal** e a **chave de conta**. PBKDF2-HMAC-SHA256 uma vez na inicialização, depois
AES-256-GCM por valor com um nonce aleatório — o mesmo vocabulário do [BSENC v1](https://docs.lacunasoftware.com/articles/bulk-signer/encryption).
Defina-a via `Signing__ProfileSecretsKey` e mantenha-a fora do controle de versão.
**A chave é mantida fora do banco de dados, e é nisso que está todo o design.** O que ela protege está
*dentro* do banco de dados: o material de certificado enviado por upload fica no banco operacional, e não
no sistema de arquivos do host, porque o disco de uma instância em cluster é efêmero e nunca
compartilhado. Assim, o banco pode guardar chaves privadas, e um arquivo de banco copiado ou uma
connection string vazada não pode equivaler a uma credencial de assinatura roubada. Uma chave guardada
ao lado do seu texto cifrado não garantiria nada disso — e é também por isso que o key ring de sessão
deliberadamente não é reaproveitado: com `Cluster:Enabled`, esse ring é, ele próprio, um conjunto de
linhas neste mesmo banco.
**O host se recusa a iniciar quando existem segredos de perfil armazenados e a chave está ausente** —
somente essa combinação, que descreve um host que subiria sem conseguir cumprir o que diz proteger. A
recusa indica a chave e a variável de ambiente, no console e no log durável.
**A importação de perfis é recusada pelo mesmo motivo, um pouco antes.** No primeiro boot que faz o seed
dos perfis a partir da configuração, um perfil que tem uma senha, um segredo de aplicação ou uma
credencial de blob é recusado, em vez de ser gravado em texto claro — o erro indica a chave, a variável
de ambiente e quais perfis têm um segredo. Este é o único passo de atualização que a 2.1.0 pode
exigir: defina a chave, inicie de novo e a importação é concluída. Uma implantação sem segredo de perfil
nunca passa por isso.
**Uma chave definida, mas errada, degrada os perfis que ela não consegue ler, e o host inicia.** Uma chave
que foi **rotacionada, restaurada de outra implantação ou digitada errado** não abre os envelopes que já
estão no banco. Cada perfil afetado é informado como degradado — em sua página, no banner de boot, no log
durável e como uma linha de prontidão; um job roteado para um deles falha com `profile.degraded`; e todo
perfil que não tem segredo continua assinando. **Não há recuperação do valor em si**: restaure a
chave original, se ela existir em algum lugar, ou informe de novo o material de certificado daquele perfil
e reinicie. Veja [Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting).
**Perdê-la significa informar de novo o material de certificado de todo perfil afetado.** Não há custódia
de terceiros (escrow), segunda chave nem caminho de recuperação, e nada disso é previsto — um segredo que
pudesse ser lido sem este valor não estaria protegido por ele. Fazer a rotação tem exatamente o mesmo
efeito que perdê-la, e a implantação não assina nada nos perfis afetados nesse meio-tempo. Faça backup
dela nos mesmos lugares em que ficam os backups da senha de criptografia, do `ApproverPortal:LinkSecret`
e do `ApproverSecondFactor:SeedSecret`, e trate a troca como uma operação que você agenda, e não como
algo que você experimenta.
Como o dashboard trata esses valores:
- **Um segredo aparece como *configurado* ou *não configurado*, nunca como valor** — na página do perfil
e no `GET /api/profiles`, que não traz nenhum segredo, credencial ou material enviado.
- **Um segredo digitado em um formulário de perfil é registrado no mascaramento de logs antes de a linha
ser gravada**, de modo que não há janela em que ele esteja armazenado e sem mascaramento. Um segredo com
menos de 12 caracteres é recusado ao salvar, porque o mascaramento por valor literal não consegue
mascarar um valor tão curto. Deixar um campo de senha em branco mantém o valor que já está no banco.
- **As páginas de perfil exigem uma sessão de navegador, e não uma chave de API sozinha.** Elas mostram
um pool de aprovadores vigente com CPFs e os dados de localização de um certificado, que a rota REST
omite; recusar a leitura só com o header impede que uma chave reaproveitada de um log de proxy leia
esses dados em um único passo. É uma restrição adicional, e não uma fronteira de segurança — o `POST /api/auth/login` aceita a mesma
chave e devolve uma sessão.
Três coisas que ela **não** é:
- **Não protege o PIN do PKCS#11.** Ele continua
[somente por variável de ambiente](https://docs.lacunasoftware.com/articles/bulk-signer/security#pin-do-pkcs11--somente-por-variável-de-ambiente), nunca
armazenado — e um PIN presente em dados de perfil armazenados é recusado exatamente como um presente na
configuração.
- **Não substitui os segredos das origens de certificado na configuração.** Referenciar um certificado
por caminho local ou por um blob de material de assinatura continua totalmente suportado.
- **Não é algo que um valor errado tolere em silêncio.** A descriptografia é autenticada, então uma chave
rotacionada, uma linha editada ou um valor movido entre colunas falha de forma explícita, em vez de
produzir uma senha aparentemente válida.
### O que um perfil degradado divulga
Um perfil cujo certificado não abre é informado em quatro superfícies, uma das quais é a prontidão. O
`/api/ready` anônimo traz o nome da linha — `signing-profile:` — e `ok: false`, e nada mais,
porque quem observa o orquestrador precisa ver *qual* linha ficou vermelha, e o nome é a única coisa na
linha que um operador escolheu. O `/api/ready/details` autenticado traz o motivo, que com frequência
cita um caminho de PKCS#12 ou de módulo PKCS#11, um endpoint de cofre ou um thumbprint. Um perfil cujos
**segredos** armazenados não podem ser descriptografados chega à mesma linha; esse motivo cita a
`Signing:ProfileSecretsKey` e o tipo de valor envolvido, e não traz caminho nem credencial. Todo motivo
passa pelo mascaramento por valor literal, de modo que uma senha, um segredo de aplicação, uma credencial
de blob ou a própria chave de segredos dos perfis são mascarados se um provedor algum dia repetir um deles
em uma mensagem.
Um perfil do método `SignerFolder` (2.16.0) acrescenta motivos próprios — um certificado sem CPF, um host
sem configurações `Signer:*`, ou uma pasta do Signer que o Lacuna Signer diz não existir —, que não trazem
caminho nem credencial; o último cita o id da pasta no Signer e o nome com que um operador a escolheu. O
`GET /api/profiles` informa uma flag `degraded` para todo perfil degradado e **nunca o motivo**, de modo
que a regra dele de omitir os dados do certificado vale por inteiro.
## ACLs de arquivo por alvo
| Alvo | Caminho | Modo | Dono |
|------|---------|------|------|
| Linux | `/etc/bulksigner` | `0750` | `bulksigner:bulksigner` |
| Linux | `/etc/bulksigner/bulksigner.env` | `0640` | `bulksigner:bulksigner` |
| Linux | `/etc/bulksigner/appsettings.Production.json` | `0640` | `bulksigner:bulksigner` |
| Linux | `/var/lib/bulksigner` | `0750` | `bulksigner:bulksigner` |
| Linux | `/var/lib/bulksigner/db` | `0700` — **defina-o você mesmo**, veja abaixo | `bulksigner:bulksigner` |
| Windows | `C:\ProgramData\Lacuna\BulkSigner` | Herda a ACL do `ProgramData`, mais uma regra `Modify` para `NT SERVICE\LacunaBulkSigner` — veja abaixo | `NT SERVICE\LacunaBulkSigner` (efetivo) |
| Docker | `./config/appsettings.Production.json` | Depende do sistema operacional do host | UID 1654 lê como uma montagem `:ro` |
| Docker | `./data/db` no host | `0700`, dono UID 1654 — **defina-o você mesmo** | UID 1654 |
O script de instalação do Linux cria o usuário de sistema, define as ACLs e nunca toca em
`/opt/bulksigner` depois da instalação inicial (o binário é `root:root`, modo `0755`). O script de
instalação do Windows **acrescenta** uma regra `Modify` para a conta virtual
`NT SERVICE\LacunaBulkSigner` na árvore de dados e não remove nada: o que quer que o `C:\ProgramData`
conceda continua valendo abaixo dele, e em uma instalação padrão isso inclui **`BUILTIN\Users` com
acesso de leitura**. Restrinja você mesmo, como abaixo.
### `db/` merece o mesmo cuidado que `keys/`, e o produto não o configura para você
Com `Database:Provider = Sqlite` — o padrão —, todo o banco operacional é um único arquivo em `db/`, e,
desde que os perfis de assinatura passaram a ficar nele, esse arquivo pode guardar **a chave privada de um
PKCS#12 enviado por upload**, além de toda senha de PKCS#12, segredo de aplicação do Key Vault e credencial de blob
armazenados. Ele deixou de ser apenas um registro do que aconteceu e passou a fazer parte da cadeia de
custódia da identidade de assinatura.
**Dois fatos que precisam ser considerados juntos.** Os valores nele são criptografados, e a `Signing:ProfileSecretsKey` é
deliberadamente mantida fora dele — então um arquivo de banco copiado, sozinho, não é uma credencial de
assinatura utilizável. Mas acesso de leitura ainda significa acesso aos beneficiários de todo pagamento, ao
CPF e ao e-mail de todo aprovador e a metade de um segredo em duas partes; e as duas metades são rotineiramente capturadas
juntas, porque a chave é uma variável de ambiente no mesmo host.
**Linux: o boot cria o `db/` com a umask do processo — não `0700`.** Com a umask usual `0022`, ele fica em
`0755`, e o que de fato impede alguém de lê-lo é que o diretório pai `0750`, `/var/lib/bulksigner`, não
pode ser percorrido. Isso é proteção por contenção, e não por intenção, e basta um `chmod` no pai para
perdê-la:
```bash
chmod 0700 /var/lib/bulksigner/db && chown -R bulksigner:bulksigner /var/lib/bulksigner/db
```
**Windows: verifique antes de presumir.**
```powershell
icacls C:\ProgramData\Lacuna\BulkSigner\data\db
```
Se `BUILTIN\Users` ou `Everyone` aparecer, toda conta interativa daquele host consegue ler o banco
operacional — o histórico de jobs assinados, nomes, CPFs e endereços de e-mail de aprovadores, e os
segredos de perfil criptografados. Corte a herança e mantenha apenas os três principais que precisam de acesso:
```powershell
icacls C:\ProgramData\Lacuna\BulkSigner\data\db /inheritance:r `
/grant "NT SERVICE\LacunaBulkSigner:(OI)(CI)M" `
/grant "SYSTEM:(OI)(CI)F" /grant "Administrators:(OI)(CI)F"
```
Aplique o mesmo a `data\keys`, o key ring de sessão, que sempre mereceu isso.
**O Docker é o caso em que a contenção não se sustenta**, porque as permissões do bind mount são as do
host e nenhum arquivo de unit as controla. Defina-as no host, e lembre-se de que qualquer coisa com acesso
de leitura àquele diretório — um agente de backup, outro container montando o mesmo caminho, um
`docker cp` — tem acesso de leitura ao banco.
**Depois, olhe além do arquivo.** Com `SqlServer`, não há `db/` e isso passa a ser responsabilidade do seu
SGBD, nos mesmos termos: [a connection string *é* a credencial](https://docs.lacunasoftware.com/articles/bulk-signer/security#a-connection-string-do-banco-operacional).
E em todo provider, um [backup do banco de dados](https://docs.lacunasoftware.com/articles/bulk-signer/retention#disciplina-de-backup) **não é
criptografado nem BSENC** — então um destino de backup herda tudo o que está acima, e uma chave privada
enviada por upload viaja dentro dele.
## Mascaramento de logs — duas camadas
Os logs estruturados duráveis fluem por um pipeline de mascaramento. Segredos são removidos em duas
camadas complementares:
1. **Mascaramento por nome de propriedade.** As propriedades de cada evento de log são percorridas, e
valores cujo nome contenha `Password`, `Pin`, `License`, `ApiKey`, `Secret`, `AccountKey`, `Salt`,
`Token`, `ConnectionString`, `Authorization`, `Cookie` ou `Cpf` (sem diferenciar maiúsculas de minúsculas) são
substituídos por `***`. A correspondência é por *substring*, então `AppSecret` e `ClientSecret` são
ambos capturados pelo token `Secret`. Isso cobre o caminho estruturado:
```cs
logger.Information("Loaded {ApiKey}", apiKey);
// → "Loaded ***"
```
2. **Mascaramento por valor literal.** Na inicialização, o serviço carrega o texto literal de cada valor
de segredo configurado (licença do PKI, a chave de API, a chave de API e o segredo do webhook do
Lacuna Signer, a chave de API do CloudHub, senhas de PFX, client secrets do Azure Key Vault, credenciais de blob, do Azure Files, dos
destinos de backup e do destino de log em tabela, o client secret do Entra ID, o segredo de link do
portal do aprovador, o segredo de semente do segundo fator, a chave de segredos dos perfis de
assinatura, a senha de criptografia, o PIN do PKCS#11, as connection strings do banco operacional e do
Application Insights) e remove essas strings exatas de toda linha de log renderizada. Segredos
declarados em *todos* os perfis de assinatura são coletados, não apenas os do bloco global
`Signing:Certificate`, e um segredo salvo na página de um perfil entra na lista no momento em que é
salvo. Isso cobre o caminho da interpolação acidental:
```cs
logger.Error($"Failure with config: {appSettingsBlob}");
// → "Failure with config: { … Auth.ApiKey: ***, Signing.PkiSdkLicense: ***, … }"
```
O mascaramento por valor literal ignora segredos com menos de 12 caracteres, para evitar
correspondências patológicas.
Tanto a saída em arquivo quanto a no console passam pelo mesmo pipeline de mascaramento. A licença do Web
PKI deliberadamente [não é um segredo](https://docs.lacunasoftware.com/articles/bulk-signer/security#a-licença-do-web-pki-não-é-um-segredo-webpkilicense) e não é
mascarada.
> **Aviso: Corrigido na 2.1.0 — links de aprovador eram gravados no log durável**
>
> Toda entrada de log gravada ao atender uma requisição traz o caminho da requisição, e em
> `/approvals/link/{token}` esse caminho termina no token de portador permanente do aprovador — então, da
> primeira versão do portal do aprovador até a 2.1.0, trocas de link bem-sucedidas registravam tokens
> inteiros nos arquivos de log e, com `Logging:AzureTable`, em uma tabela que nada poda. Agora o
> mascaramento oculta o segmento secreto desse caminho. **O que já foi gravado continua gravado**, e não é
> possível fazer a rotação do token por pessoa: se seus logs anteriores à 2.1.0 foram legíveis por alguém que não
> deveria poder aprovar arquivos de pagamento, trate esses links como divulgados e troque o
> `ApproverPortal:LinkSecret`, que reemite todos os links de uma vez.
**As duas camadas ficam no caminho do log; por isso, toda superfície que mostra uma falha em outro lugar
faz o próprio mascaramento.** Mascarado, e não omitido, porque estes são os únicos lugares em que você
descobre por que algo deu errado sem abrir um arquivo de log:
| Onde você a vê | O que vale saber |
|---|---|
| **O dashboard** — o tooltip de falha de atualização de uma página, um snackbar vermelho depois de um **Reescanear**, **Tentar novamente** ou **Cancelar** que falhou, o último erro de uma pasta monitorada na página **Pastas de entrada**, o motivo de o destino de backup estar inacessível | A mensagem do próprio provedor, com todo segredo configurado mascarado como `***`. O último erro de uma pasta também é o `lastError` do `GET /api/system/folders` |
| **A narração do console** — que um serviço do Windows, uma unit do systemd ou um container entregam ao Visualizador de Eventos, ao journald ou ao `docker logs` | Toda linha, inclusive os painéis de resumo do boot. Um lugar tão durável quanto o arquivo de log |
| O **texto de falha de um job** em sua linha do tempo, na página do job e no `GET /api/jobs/{id}` | Mascarado antes de ser armazenado, de modo que a própria linha de auditoria fica limpa, e não apenas a exibição dela |
| As **linhas do `/api/ready/details`** e os mesmos detalhes na página **Sistema** | Onde um problema de credencial do Azure Table ou do SQL Server aparece depois de uma rotação |
O que esse mascaramento consegue pegar é limitado exatamente como o do log durável: ele mascara valores
com que o host foi configurado, então o produto não consegue reconhecer uma credencial que você nunca
forneceu a ele. Há um lugar que deliberadamente mostra *menos*: uma connection string que o produto não
consegue interpretar é informada como não interpretável, sem ser citada, porque o trecho citado seria
exatamente o que você digitou.
## As superfícies de aprovação
Relevante apenas quando um perfil de assinatura tem um [bloco `Approval`](https://docs.lacunasoftware.com/articles/bulk-signer/approvals). Nada nesta
seção existe em uma implantação que não usa a etapa de aprovação.
### Dados pessoais dos aprovadores — CPF e e-mail
Os aprovadores trazem os primeiros dados pessoais que o produto mantém sobre *seus próprios operadores*,
e não sobre os beneficiários de um arquivo de pagamento. Três regras se aplicam, e não são a mesma
regra:
- **O CPF é mascarado no caminho estruturado.** `Cpf` está na lista de tokens de nome de propriedade —
por um motivo diferente de todo o resto que está lá: vazá-lo não deixa um atacante entrar, expõe uma
pessoa física.
- **O CPF serve apenas para exibição e auditoria.** Nenhuma lógica depende dele e nenhuma busca é feita
por ele. Ele existe para que um registro de auditoria identifique uma pessoa, e não uma caixa de correio.
- **O e-mail é mascarado para exibição, não removido.** `maria@empresa.com.br` é renderizado como
`m***@empresa.com.br` na narração de terminal e nas linhas de log, que sobrevivem ao job no
scrollback. O endereço completo continua recuperável no snapshot de aprovação do job — nunca mascare
algo que você também precisa consultar.
Ambos os valores são retidos no snapshot de aprovação do job depois de o job chegar a um status
terminal, e copiados novamente para cada linha de aprovação registrada. Essa é a decisão oposta à do
detalhe de linhas do CNAB240, que *é* expurgado — veja [Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention).
**Em uma aprovação registrada por assinatura, a linha também registra o certificado**: o titular e o
emissor, o número de série, o thumbprint SHA-256, o CPF que consta no certificado e, no certificado de
uma empresa, seu CNPJ — todos vazios em uma decisão por clique. Eles respondem a uma pergunta diferente
da do pool: o pool diz quem estava *autorizado* a decidir, e o nome e o CPF continuam vindo dele; o
certificado diz qual *chave confirmou* a decisão. Em todo lugar em que o CPF do certificado sai da
linha — a linha do tempo, o evento operacional, o log, e o objeto `certificate` do
`GET /api/jobs/{id}/approvals` — ele é mascarado até os dígitos verificadores, **inclusive dentro do
titular**, já que o nome comum de um certificado ICP-Brasil muitas vezes é escrito `NOME:CPF`. O CNPJ
nunca é mascarado, já que identifica uma empresa. Os bytes da assinatura não estão na linha: o arquivo
entregue em `output/` é a prova.
### A página de aprovação por job não é autenticada
**Qualquer um que consiga abrir o link de aprovação de um job pode aprovar — ou rejeitar — como qualquer
pessoa do pool congelado daquele job.** A página em `/approve/{jobId}` e a rota atrás dela
(`POST /api/approvals/{id}`) não exigem credencial, e nada verifica se a pessoa que seleciona um
endereço é a dona dele.
- **Trate a URL de aprovação como uma capacidade (capability).** Envie-a apenas para as pessoas do pool, por um canal
que você usaria para o próprio arquivo de pagamento, e diga a elas para não a repassar — um link
repassado basta para uma pessoa satisfazer um quórum de várias. O produto não envia e-mail e, desde a
2.9.0, a página do job do operador não entrega mais o link; um aprovador com acesso ao portal chega a um
arquivo retido pela própria fila.
- **A mesma URL também pode barrar um arquivo de pagamento.** As consequências são assimétricas — uma
aprovação não autorizada movimenta dinheiro, uma rejeição não autorizada o atrasa e exige um novo
envio — o que faz da rejeição a metade menos perigosa da capacidade, e não uma metade inofensiva.
- **Ids de job são GUIDs v4**, então a URL não é adivinhável na prática, e a rota tem sua própria
cota de limite de requisições (rate limiting) (`RateLimiting:Approval`, dez por minuto por endereço por
padrão).
- **As recusas são deliberadamente genéricas.** Um endereço bem formado que não está no pool e uma
string que não é endereço nenhum retornam, ambos, `approval.unknown-approver`.
- **Toda decisão registra quão fraca era sua identificação** — `SelfDeclaredEmail`, mais o endereço IP e
o user agent da requisição. O `IpAddress` é o endereço remoto da conexão: atrás de um proxy reverso
esse é o proxy, a menos que os headers encaminhados (forwarded headers) estejam configurados.
- **O nome e o CPF em uma linha de aprovação vêm do pool congelado, nunca da requisição.**
- **O banner de inicialização exibe um aviso para cada perfil com aprovação configurada**, a cada boot.
- **Em um job cujo conjunto de assinantes congelado inclui os aprovadores, uma aprovação é uma
assinatura, e não um clique.** A rota anônima recusa um clique em um job assim com
`approval.signature-required`; o aprovador assina pelo portal, ou por esta página com uma sessão
identificada.
Se uma implantação não pode aceitar essa exposição, mantenha o serviço fora de qualquer rede que os
navegadores dos aprovadores consigam alcançar, ou habilite o
[portal do aprovador](https://docs.lacunasoftware.com/articles/bulk-signer/security#o-portal-do-aprovador-e-quanto-vale-um-link-durável), o
[login pelo Entra ID](https://docs.lacunasoftware.com/articles/bulk-signer/security#modo-de-login-pelo-microsoft-entra-id-opcional), o
[segundo fator](https://docs.lacunasoftware.com/articles/bulk-signer/security#o-segundo-fator-e-quanto-ele-vale) ou as assinaturas de aprovador, cada um dos quais
reduz consideravelmente essa exposição.
### O que a superfície anônima divulga, e o que ela omite
A página de aprovação mostra os pagamentos individuais, porque só o total não dá a uma pessoa nada para
conferir. Isso a torna uma divulgação deliberada de nomes de beneficiários e valores a quem tiver o
link. Três regras a limitam:
- **Mascarado: identificação e conta.** O CPF/CNPJ do beneficiário é reduzido aos seus dígitos
verificadores (`***.***.***-09`) e a conta de destino aos seus últimos dígitos, com a agência omitida
(`***149-4`) — o suficiente para distinguir duas pessoas homônimas e para ver que uma conta mudou, não
o suficiente para identificar ou pagar alguém.
- **Não mascarado, de propósito: nome, valor, data de pagamento, segmento.** Eles *são* a base da
decisão. Mascará-los tornaria a página inútil — e uma etapa de aprovação inútil é um resultado de
segurança pior do que uma que divulga dados, porque passa a ser aprovada no automático.
- **Mascaramento não é autenticação e não é oferecido como tal.** Ele limita o que um estranho com o
link descobre; não impede que ele descubra. Duas contas que diferem apenas nos dígitos iniciais ficam
idênticas depois de mascaradas.
Duas capacidades são negadas em toda superfície de aprovação — a página anônima, o portal e a página do
job que um aprovador pode abrir:
- **Sem download do arquivo bruto.** A tabela renderizada é limitada e serve à decisão; o arquivo é um
dump completo, legível por máquina, do CPF e da conta bancária de cada beneficiário, em um formato
feito para processamento em lote. O `GET /api/jobs/{id}/output` exige credenciais de operador. O mesmo
vale para o **arquivo compactado de saídas assinadas** do operador — `GET /api/jobs/archive`, um ZIP
com os arquivos assinados de vários jobs `Completed`, oferecido na página Jobs —, que nenhuma credencial
de aprovador satisfaz: são os mesmos arquivos que o operador já coleta do `output/`, vários de uma
vez, e isso não muda quem pode ter acesso aos bytes. Desmascarar a tabela para um aprovador
identificado **não** liberou os bytes.
- **Sem índice *anônimo* de aprovações pendentes.** O portal do aprovador é um índice, mas é protegido por
uma política de autorização e lista apenas os jobs cujo pool congelado inclui a pessoa que o está lendo.
Ninguém abaixo de um operador consegue obter o mapa de todo arquivo de pagamento na fila.
### A exportação da fila está do outro lado dessa regra, não é uma exceção a ela
Um aprovador pode baixar a aba do portal que está lendo como uma pasta de trabalho `.xlsx`, protegida
pela política `Approver` e com sua própria cota de limite de requisições. A distinção em relação ao
download negado é **a unidade do que sai**:
- **O download bruto negado** é cada beneficiário de um arquivo de pagamento — nome, CPF/CNPJ, agência,
conta — em um formato feito para máquinas.
- **A exportação da fila** é uma linha por *arquivo* de pagamento: nome do arquivo, perfil, status, nome
e CPF/CNPJ do pagador, total geral, contagens de pagamentos e exclusões, maior pagamento individual,
timestamps, a contagem de aprovações e a decisão do próprio leitor. **Nenhuma linha de pagamento
chega a ela** — nenhum nome de beneficiário, identificação fiscal, agência ou conta aparece na pasta
de trabalho.
Ela é delimitada pela sessão e por nada mais: não há valor de rota, parâmetro de consulta (query) ou
header pelo qual um chamador pudesse exportar como outra pessoa, e a chave de API de um operador ou seu
cookie de dashboard não dão acesso a ela. É somente leitura e registrada para auditoria como uma linha de
log que indica a lista, a contagem de linhas e o endereço mascarado do aprovador.
A exportação **do operador** — `GET /api/jobs/export`, o *Exportar para Excel* da
[página Jobs](https://docs.lacunasoftware.com/articles/bulk-signer/dashboard#jobs--jobs) — tem o mesmo formato do lado da outra credencial: uma linha por job,
todo valor tirado da linha do job, nenhuma linha de pagamento, protegida pela política de operador e não
oferecida em nenhuma superfície de aprovação. Ela muda apenas o formato de uma lista que o operador já
vê, e não quem pode ter acesso a ela.
> **Aviso**
>
> Uma pasta de trabalho é uma cópia repassável que o produto não consegue recolher. Linhas no nível do job
> ainda identificam os arquivos de pagamento de uma empresa, seus valores e sua identificação de pagador. O
> limite de requisições restringe a rapidez com que cópias podem ser feitas; nada restringe o que acontece
> com uma delas.
> Trate uma pasta de trabalho como você trata os próprios arquivos de pagamento.
### O portal do aprovador, e quanto vale um link durável
Quando `ApproverPortal:Enabled` está ligado, cada aprovador de um pool tem uma URL pessoal permanente,
trocada uma vez por dispositivo por um cookie de sessão, que abre uma fila restrita aos pools de que ele
participa.
- **O link é uma credencial de portador (bearer) sem expiração.** Ele é substancialmente mais forte que o link por
job em um aspecto — o detentor não pode decidir *como outra pessoa*, porque o portal não oferece campo
de endereço — e mais fraco em outro: ele não expira com um arquivo de pagamento.
- **Distribua-o como uma senha.** Um link por pessoa, enviado privadamente. A página **Sistema** os
mostra como campos somente leitura para copiar, e não como links clicáveis.
- **Um pool é editado na página do perfil e em nenhum outro lugar.** Nenhuma rota grava um pool, porque
uma chave capaz de acrescentar um endereço a um pool de aprovação de pagamentos é uma chave capaz de
aprovar pagamentos. Como os perfis ficam no banco operacional, editar
`Signing:Profiles[].Approval.Approvers[]` na configuração **não revoga ninguém** — essa seção é um seed
inerte depois do primeiro boot.
- **Revogar uma pessoa** significa removê-la do pool de aprovadores de todo perfil, na página do perfil.
A resolução lê os pools como estão no banco naquele momento, então o token dela deixa de ser resolvido
de imediato, sem reinício. Desabilitar um perfil não revoga ninguém — só a remoção revoga. **Revogar todo
mundo** significa mudar o `ApproverPortal:LinkSecret`.
- **Uma sessão já obtida pela troca é uma credencial separada, que nenhuma dessas duas medidas
alcança.** Ela dura `ApproverPortal:SessionLifetime` (30 dias por padrão) com expiração **deslizante**,
então a sessão de um aprovador que continua usando o portal nunca expira, e mudar o `ApproverPortal:LinkSecret` revoga links, não
cookies. O que uma sessão assim ainda pode decidir continua limitado pelo snapshot de aprovação
congelado de cada job. Para encerrar uma antes, encurte o tempo de vida da sessão ou rotacione o
[key ring de sessão](https://docs.lacunasoftware.com/articles/bulk-signer/security#o-key-ring-de-sessão-e-onde-ele-fica) — o que desconecta os operadores também.
- **O `ApproverPortal:LinkSecret` é o segredo mais valioso que esta funcionalidade introduz.** Lê-lo
equivale a ter o link de todo aprovador. Defina-o por variável de ambiente, mantenha-o fora do
controle de versão e faça a rotação dele se suspeitar de exposição. Mínimo de 32 caracteres, exigido no
boot.
- **A sessão tem um esquema de autenticação próprio.** A chave de API ou o cookie de dashboard de um
operador não abrem o portal, e a sessão de um aprovador não satisfaz nenhuma política de operador — com
uma exceção deliberada: `/jobs/{id}`, protegido por uma política própria, acessível somente para jobs
cujo pool congelado inclui o aprovador, e com os CPFs do pool e as ações **Tentar novamente** /
**Cancelar** / **Baixar arquivo de saída** todos ocultos. Desde a 2.9.0, essa página não exibe link de
aprovação por job para leitor **nenhum**; antes, ele era ocultado dos aprovadores como um controle de
**quórum**, porque permite a quem o tem aprovar como qualquer membro do pool.
- **A aba *Aprovados* é limitada** pelo `ApproverPortal:DecidedLookback` (90 dias por padrão), que é o que
impede um link roubado de valer todo o histórico de pagamentos de uma implantação.
### Não existe endpoint REST de aprovação
O estado de aprovação é **legível** por REST — o `GET /api/jobs/{id}` traz um resumo `approval` e o
`GET /api/jobs/{id}/approvals` retorna o pool congelado e a lista de decisões, ambos protegidos pela
política comum de chave de API ou cookie. Em uma decisão registrada por assinatura, um objeto
`certificate` traz o titular, o emissor, o número de série e o thumbprint, o CPF do certificado mascarado e seu CNPJ
inteiro; `null` em uma decisão por clique. **Nenhuma rota REST *autenticada* registra uma decisão**, e
essa assimetria é uma decisão de projeto, e não uma lacuna na superfície. Protegida pela chave de API,
essa rota seria *pior* que a página não autenticada: a chave já fica na configuração de um ERP, em um
pipeline de implantação e em um arquivo de configurações de produção, então "um aprovador decidiu"
significaria "alguma coisa que tem a credencial de operador decidiu".
A única rota que de fato registra uma decisão, `POST /api/approvals/{id}`, é anônima e concede a mesma
capacidade que o link de aprovação. Habilitar o segundo fator **a retira por completo** em vez de
autenticá-la, pelo mesmo motivo — veja [abaixo](https://docs.lacunasoftware.com/articles/bulk-signer/security#o-segundo-fator-e-quanto-ele-vale).
Uma aprovação **assinada** está do mesmo lado da linha. Nenhuma rota aceita uma assinatura: a assinatura
é feita pelo navegador, a partir do portal ou da página por job com uma sessão de aprovador, e a única
rota de navegador envolvida é o callback de assinatura em nuvem abaixo.
### O callback de assinatura em nuvem é um GET que altera estado, deliberadamente
Quando um aprovador assina com um certificado mantido por um provedor de nuvem, pelo Lacuna CloudHub
(acrescentado na 2.7.0), o provedor manda o navegador dele de volta para
`GET /approvals/cloud/return?state=…&session=…`, e toda a aprovação assinada é executada nessa
requisição. Um redirecionamento de provedor só pode ser um `GET`, então a rota altera estado em uma
requisição desse tipo, e quatro mecanismos substituem as garantias que um `POST` daria:
- **A rota exige a sessão de aprovador** — o cookie do portal ou um aprovador do Entra, nunca uma
chave de API e nunca anônima. Os dois cookies são `SameSite=Lax`, e é por isso que uma navegação de
nível superior (top-level) que volta do provedor os envia. Sem a sessão, a rota responde com o caminho
de login do aprovador **puro**, sem endereço de retorno, para que o valor de `session` nunca vá parar
no histórico do navegador.
- **O valor de `state` é um token aleatório criado quando o aprovador escolheu a nuvem**, armazenado na
requisição pendente e em nenhum outro lugar, e confrontado com uma requisição que precisa indicar o
mesmo aprovador que o cookie indica. Um link forjado traz o cookie da vítima, mas não um token emitido
pela requisição dela; um token roubado não vem acompanhado de cookie. Nos dois casos, há uma única
recusa genérica, com redirecionamento ao portal e nada registrado — o portal informa uma vez que a
assinatura não pôde ser associada (`?cloud=unmatched`, nunca o motivo). Os quatro motivos — desconhecido, de outro aprovador, já
consumido, com mais de quinze minutos — são distinguidos apenas no log. A requisição é reivindicada
antes de a assinatura ser executada, então um callback repetido (replay) é recusado, em vez de ser
executado duas vezes.
- **O valor de `session` é um token de portador da sessão do CloudHub** — quem o tem pode assinar com o
certificado que o provedor abriu —, então nenhuma resposta de erro repete o endereço da requisição e
nenhum dos dois valores da query string chega a uma linha de log.
- **A `CloudHub:ApiKey` é um segredo**, diferentemente da licença do Web PKI: uma credencial de portador
para toda sessão que este host cria. Defina-a pela variável de ambiente em uma instalação como serviço; ela é
registrada nas duas camadas de mascaramento. Veja [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration).
A sessão do CloudHub é criada com o CPF congelado para o aprovador naquele arquivo — o aprovador não
digita nenhum CPF e não consegue, a partir deste produto, abrir uma sessão com o CPF de outra pessoa — e o
certificado que volta é verificado exatamente como o de um navegador: cadeia, validade, revogação, CPF
presente, CPF do membro congelado, tudo antes de qualquer coisa ser assinada. O resultado é mostrado uma
vez na página de onde o aprovador saiu; não há página de resultado nem resultado em uma URL.
> **Observação: Corrigido na 2.14.0**
>
> Quando a leitura de um certificado em nuvem falhava, o motivo registrado em Warning, gravado no destino
> de log em tabela e mostrado ao aprovador citava o endereço da requisição — cuja query string é o id de
> sessão do CloudHub, um token de portador que permite assinar com o certificado em nuvem do aprovador até
> o CloudHub expirá-lo. Agora o motivo identifica a chamada pelo caminho e pelo status, e o id de sessão é
> mascarado onde quer que as mensagens do próprio CloudHub possam repeti-lo.
### A sessão do CloudHub de uma aprovação em lote fica retida, criptografada, por alguns minutos
Um lote aprovado com um certificado em nuvem (acrescentado na 2.14.0) volta pelo mesmo callback, com as
mesmas recusas, mas o callback **não assina nada**: ele armazena o valor de `session` no lote pendente do
aprovador e redireciona para o portal, que executa o lote. Durante esses minutos, o banco guarda um token de
portador capaz de assinar com o certificado em nuvem do aprovador, e três mecanismos o limitam:
- **Ele é criptografado com o key ring de sessão** em que os cookies de aprovador já se apoiam, com um
propósito (purpose) próprio. Sem `Cluster:Enabled`, o ring fica em `keys/`, fora do arquivo de banco,
então um backup ou um banco copiado nesses minutos não traz o que o abre. No modo cluster, o ring é um
conjunto de linhas no mesmo banco, e uma cópia do banco o abre — a mesma cópia já permite forjar o
cookie de um aprovador.
- **Ele é apagado no momento em que o lote é reivindicado.** O portal reivindica o lote com uma
atualização que só tem sucesso para um único chamador e esvazia a coluna na mesma gravação; a partir
daí, a sessão fica apenas na memória, durante uma execução. Um lote que não volta em quinze minutos não aprova
nada, e sua sessão também é apagada.
- **O tempo de vida da própria sessão é um pedido, e não uma garantia.** O produto pede ao CloudHub uma
sessão de quinze minutos; o CloudHub repassa isso ao provedor e não consegue encerrar uma sessão antes do
prazo. Uma sessão que um provedor concedeu por mais tempo continua válida lá depois de o produto a ter
descartado — e é por isso que o limite efetivo é o que o próprio produto impõe.
A assinatura em nuvem de um único arquivo nunca retém a sessão: ela é executada na requisição de
callback, como descrito acima, em uma sessão que só consegue assinar uma vez.
### O segundo fator, e quanto ele vale
O `ApproverSecondFactor:Enabled` acrescenta um pedido de TOTP antes da decisão de um aprovador, uma vez
por janela de verificação por sessão de navegador. O que ele resolve é precisamente a **sessão sem
supervisão**: uma máquina deixada autenticada, ou um link de portal lido por quem não deveria tê-lo, não
decide mais por conta própria. A janela é absoluta e pertence ao navegador, e não à pessoa — é isso que
garante esse efeito. Com o login pelo Entra, sair apaga a janela da sessão, de modo que o novo login
silencioso de um colega em uma estação compartilhada começa sem ela.
Três limites para ter em mente, porque cada um é uma garantia que este controle **não** oferece:
- **Ele não impede que um operador se passe por aprovador.** TOTP é simétrico: um operador pode ler todo
link de aprovador e redefinir toda inscrição, então um operador ainda pode ser qualquer aprovador. O que
resolve isso é material de chave que só o aprovador tem: em um perfil cujo conjunto de assinantes
inclui os aprovadores, cada aprovação é uma assinatura ICP-Brasil verificada em relação ao CPF do pool
congelado, que um operador não consegue produzir — e uma aprovação assinada assim satisfaz o segundo
fator por si só. Em um perfil cujas aprovações são por clique, o segundo fator não deve ser descrito
como se tivesse resolvido isso.
- **Ele não reduz o que um link repassado divulga.** Com o fator ligado, um leitor não identificado
de `/approve/{jobId}` obtém a mesma visão somente leitura com o mesmo mascaramento de antes — apenas a
*capacidade* de decidir é retirada.
- **Ele retira o `POST /api/approvals/{id}` por completo** em vez de condicioná-lo, porque somente uma
sessão de navegador pode demonstrar uma presença comprovada. Veja
[Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals#provando-que-é-você).
**O `ApproverSecondFactor:SeedSecret` é um segredo sem procedimento de rotação.** Ele é a chave com a
qual a semente do autenticador de cada aprovador é criptografada em repouso (PBKDF2-HMAC-SHA256 →
AES-256-GCM), tem no mínimo 32 caracteres e é obrigatório sempre que o fator está ligado — o banco pode
ser o próprio SGBD do cliente, então as sementes nunca ficam em texto claro nele. As sementes são
aleatórias por aprovador, e não derivadas, então ter o primeiro fator não permite gerar o segundo.
**Perdê-lo ou mudá-lo significa que todo aprovador precisa se inscrever de novo**, o que é uma operação
coordenada, e não uma simples edição de configuração. Forneça-o por variável de ambiente e guarde-o com
o mesmo cuidado do `ApproverPortal:LinkSecret`.
### Uma aprovação é vinculada a bytes
Imediatamente antes de assinar, o hash da cópia em staging é recalculado e comparado com o hash
registrado no momento da interpretação. Uma divergência faz o job falhar com `approval.content-changed` —
nunca uma reinterpretação silenciosa, nunca um prosseguimento. Sem isso, "estas pessoas autorizaram este
arquivo de pagamento" deixaria de ser verdade exatamente no momento em que uma assinatura o torna
definitivo.
Em um perfil cujo conjunto de assinantes inclui os aprovadores, o vínculo é **também criptográfico**: a
própria chave de cada aprovador assina os bytes em staging, e a etapa de assinatura só promove o
resultado se a verificação encontrar **exatamente** os certificados dos aprovadores registrados, por
thumbprint, mais a chave do perfil quando a regra congelada assim determina — a falta de um assinante faz
o job falhar, e um assinante a mais também.
## Envelope de erro REST — o que é e o que não é exposto
Toda resposta de erro traz um slug estável legível por máquina na extensão `code` (por exemplo
`job.not-found`, `upload.too-large`, `rate-limited`, `auth.invalid-credentials`, `internal`). Veja
[API REST](https://docs.lacunasoftware.com/articles/bulk-signer/rest-api) para a tabela completa.
Em `Production`:
- O componente que personaliza os erros remove `detail`, `instance` e qualquer extensão além de `code`,
`traceId`, `requestId`, `errors`. Nenhum stack trace chega aos clientes.
- `code = "internal"` é aplicado às respostas 500 geradas pelo framework, `code = "auth.invalid-credentials"`
às 401 e `code = "rate-limited"` às 429.
Em `Development`, os detalhes completos (inclusive mensagens de exceção) são repassados para facilitar a
depuração — **nunca execute com `ASPNETCORE_ENVIRONMENT=Development` em um host de produção.**
## Exposição de rede
- O serviço escuta em HTTP puro em `0.0.0.0:8080` por padrão — termine o TLS em um proxy reverso
(nginx, IIS, Traefik).
- `Hosting:RequireHttps = true` ativa o redirecionamento HTTPS no próprio processo; combine-o com uma
configuração de certificado no Kestrel.
- O banner de resumo de prontidão na inicialização imprime `https redirect = on/off`, de modo que uma
chave digitada errado aparece imediatamente.
- O `/api/metrics` é protegido pela mesma política por padrão (`Metrics:RequireApiKey = true`).
Defina-o como `false` somente quando o coletor Prometheus estiver dentro do perímetro de confiança.
- O `/api/ready` é anônimo por padrão e não traz detalhes; o
[`Readiness:RequireApiKey = true`](https://docs.lacunasoftware.com/articles/bulk-signer/security#prontidão-o-veredito-é-anônimo-o-diagnóstico-não) o protege onde o
probe consegue enviar a chave.
- `Upload:Enabled = false` remove a superfície de upload: o `POST /api/files` responde
`409 upload.disabled` antes de resolver um perfil, e a página **Jobs** não exibe o botão de upload. O
host passa então a receber arquivos apenas de suas pastas monitoradas.
- O limite de requisições vem ligado por padrão (`RateLimiting:Enabled = true`). Desabilite-o apenas em
instalações em rede fechada.
### Em qual endereço de cliente o produto acredita
Duas coisas dependem do endereço do cliente; por isso, atrás de um proxy ou balanceador de carga, isto
é uma configuração de segurança, e não um detalhe: a **partição do limite de requisições** e o
**endereço registrado em cada aprovação** — um dos controles compensatórios da rota de aprovação
anônima. Atrás de um proxy sem tratamento de headers encaminhados, todo chamador chega de um único
endereço: a cota por cliente vira uma única cota compartilhada pelo mundo inteiro, e o endereço
registrado não diz nada sobre quem decidiu.
O `Hosting:ForwardedHeaders:Enabled = true` corrige isso, e **exige um conjunto de confiança** — um de
`TrustAnyProxy`, `KnownProxies` ou `KnownNetworks`, ou o boot é recusado em vez de adotar por padrão a
opção mais ampla. Duas regras que vale deixar claras:
- **`TrustAnyProxy = true` é correto no Azure App Service e perigoso em um proxy reverso.** O front end
do App Service não tem endereço estável a listar; uma implantação com proxy reverso que confia em
qualquer um significa que quem alcançar o Kestrel diretamente pode se declarar qualquer endereço de
cliente. Restrinja o acesso à origem se usar essa opção — veja
[Azure App Service](https://docs.lacunasoftware.com/articles/bulk-signer/azure#entrada--front-door-na-frente-do-app).
- **Definir o `ASPNETCORE_FORWARDEDHEADERS_ENABLED` do framework ao lado dele é recusado no boot.** Cada
um acrescenta seu próprio processamento, então os headers seriam tratados duas vezes e um
`ForwardLimit` de 1 aceitaria silenciosamente dois saltos.
O banner de resumo de prontidão imprime `forwarded headers = …` indicando o conjunto de confiança, e não
apenas `on`. No
[modo cluster](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability#as-cotas-do-limite-de-requisições-são-por-instância-então-o-limite-efetivo-é-n),
lembre-se de que cada instância aplica sua própria cota, então o limite efetivo por cliente é ×N.
## Postura forense
- **Trilha de auditoria.** Toda transição de estado grava uma entrada de histórico de job no banco
operacional; toda pausa/retomada, edição de perfil, decisão de aprovação e Limpar Jobs grava um evento
operacional. Esses registros persistem após reinicializações e sobrevivem à desinstalação (a menos
que `--purge` seja usado), e desde a 2.13.0 os eventos são legíveis por operadores na página `/events` e
pelo `GET /api/events`.
- **O que os remove.** Dentro do produto, apenas uma ordem do operador. O **Limpar Jobs** apaga toda linha
de job e todo evento operacional registrado antes de a limpeza começar, e grava um evento
`JobsCleared` — quem, quando e quantos itens de cada tipo foram apagados — como registro do corte. **Excluir um
job** remove as linhas daquele job e mantém todo evento, acrescentando um `JobDeleted` que resume as
aprovações que ele tinha; não há rota REST para isso, então uma chave de API vazada não consegue
usar essa ação. Veja
[Retenção](https://docs.lacunasoftware.com/articles/bulk-signer/retention#o-que-um-operador-pode-apagar-limpar-jobs-e-exclusão-de-job).
- **Correlação por requisição.** As respostas de erro incluem `traceId` e `requestId`; os mesmos ids
aparecem nos logs de arquivo, de modo que falhas do lado do cliente possam ser rastreadas até a linha
que as gerou.
- **Backup antes de atualizar — e antes de um Limpar Jobs.** Sempre faça backup do banco operacional antes
de uma atualização: a migração é executada na inicialização e é de mão única. Com `Sqlite`, o serviço
pode fazer o backup para você; caso contrário, copie o `db/bulksigner.db` com o serviço parado. Um
artefato de backup é uma cópia completa da trilha de auditoria, então herda a sensibilidade do banco
onde quer que vá parar.
---
**A seguir:** [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations) — operação do dia a dia e o ciclo de vida do job.
**Anterior:** [Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates).
# [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/operations)
Operação do dia a dia do Lacuna Bulk Signer: como iniciar, parar, reiniciar, observar e pausar o
pipeline de assinatura, e como entender o que ele faz.
## Comandos de ciclo de vida por alvo
| Alvo | Iniciar | Parar | Reiniciar | Status |
|------|---------|-------|-----------|--------|
| Linux (systemd) | `sudo systemctl start bulksigner` | `sudo systemctl stop bulksigner` | `sudo systemctl restart bulksigner` | `systemctl status bulksigner` |
| Windows | `Start-Service LacunaBulkSigner` | `Stop-Service LacunaBulkSigner` | `Restart-Service LacunaBulkSigner` | `Get-Service LacunaBulkSigner` |
| Docker | `docker compose up -d` | `docker compose stop` | `docker compose restart` | `docker compose ps` |
| Console | execute o executável publicado | `Ctrl+C` | execute de novo | `/api/health` |
A unit do systemd usa `Type=notify` — o `systemctl status bulksigner` informa `active (running)` apenas
**depois** que todo o bootstrap (carregamento da licença + migrações + recuperação do pipeline) for
concluído com sucesso. O mesmo vale no Windows: o serviço só é marcado como "Em execução" depois que o
banner de resumo de prontidão é impresso.
## Onde ficam os logs
| Alvo | Caminho |
|------|---------|
| Linux | `/var/log/bulksigner/bulksigner-yyyyMMdd.log` |
| Windows | `C:\ProgramData\Lacuna\BulkSigner\logs\bulksigner-yyyyMMdd.log` |
| Docker | `/var/log/bulksigner/` dentro do container — montado por bind em `deploy/docker/logs/` no host |
| Console | `data/logs/bulksigner-yyyyMMdd.log` (relativo ao diretório de trabalho) |
Os logs são rotacionados diariamente, com 50 MB por arquivo (configurável) e 14 arquivos mantidos por
padrão. Cada linha é texto puro, com propriedades estruturadas no final:
```text
2026-05-26T15:42:11.1234567+00:00 [INF] Worker started job 9b62… {JobId: "9b62…", Format: "Pades"}
```
Esse formato funciona bem com `tail -f` para os operadores e pode ser interpretado estruturalmente por
ferramentas forenses.
Os eventos no nível do serviço vão para:
| Alvo | Onde |
|------|------|
| Linux | `journalctl -u bulksigner` (ciclo de vida + saída padrão) |
| Windows | Visualizador de Eventos → Logs do Windows → Aplicativo (somente ciclo de vida do serviço — os logs da aplicação ficam no destino de arquivo) |
| Docker | `docker compose logs -f bulksigner` |
| Console | O terminal |
Tanto a saída em arquivo quanto a do console passam pelo pipeline de mascaramento de segredos. Veja
[Segurança](https://docs.lacunasoftware.com/articles/bulk-signer/security#mascaramento-de-logs--duas-camadas).
## A máquina de estados do job
São oito estados: um desfecho terminal "bom" (`Completed`) e dois desfechos terminais "ruins" (`Failed`,
`Canceled`). Dois dos oito são **esperas, e ambas são opcionais**: só passam por `AwaitingSigner` os jobs
cujo perfil usa `Method = LacunaSigner` (veja
[Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer)), e só passam por `AwaitingApproval` os jobs cujo
perfil tem um [bloco `Approval`](https://docs.lacunasoftware.com/articles/bulk-signer/approvals).
[](https://docs.lacunasoftware.com/images/bulk-signer/job-lifecycle-pt.png)
*Clique no diagrama para abri-lo em tamanho real.*
A figura é a versão em linguagem simples. Ela incorpora três transições menos comuns — cancelar um job
que ainda está na fila e um assinador remoto recusar ou ser cancelado — aos cartões *Cancelado* e
*Falhou*. A máquina de estados completa vem a seguir.
```text
┌─────────┐ cancel do operador ┌──────────┐
│ Queued ├────────────────────▶│ Canceled │ (terminal)
└────┬────┘ └──────────┘
captura do worker │
▼
┌────────────┐ assina local ok ┌───────────┐ verifica ok ┌───────────┐
│ Processing ├─────────────────▶│ Verifying ├─────────────▶│ Completed │
└─┬────────┬─┘ └─────┬─────┘ └───────────┘
│ │ │ verificação falha
perfil exige │ │ despacha ao ▼
aprovação │ │ Lacuna Signer ┌────────┐
▼ ▼ │ Failed │ (terminal)
┌──────────────────┐ ┌────────────────┐ └────────┘
│ AwaitingApproval │ │ AwaitingSigner │
└──────────────────┘ └────────────────┘
│ │ │ │
│ │ │ └─ recusado / expirado / timeout ─▶ Failed
│ │ └─ concluído → bytes baixados ────────────▶ Verifying
│ └─ rejeitado* / cancel do operador / prazo expirado ────▶ Canceled
└─ quórum atingido ──▶ volta a Queued (reentra na fila comum)
Failed ──retry do operador──▶ um NOVO job Queued (ParentJobId definido; o job falho segue Failed)
Canceled ──retry do operador──▶ um NOVO job Queued — só para um job que assina um documento do Signer
* um arquivo rejeitado é devolvido a output/ como .reject; a cópia preparada de um
job cancelado ou expirado vai para error/
```
Um job que assina um documento do Lacuna Signer (`Method = SignerFolder`, 2.16.0) tem um desfecho que o
diagrama não mostra: `Processing → Canceled`, quando o documento seguiu adiante no Signer antes de este
host assiná-lo. Nenhum job de arquivo chega a ele — veja
[Assinando em uma pasta do Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#assinando-em-uma-pasta-do-lacuna-signer--signerfolder).
Regras principais:
- **`AwaitingApproval` tem exatamente três transições**, e `Failed` deliberadamente não é uma delas.
Nenhum recurso fica preso a um job retido — nenhum worker, nenhum slot, nenhum serviço remoto —, então
nada está em posição de fazê-lo falhar. Ele é liberado de volta para `Queued`, é cancelado ou continua
esperando. Três situações diferentes levam a essa mesma transição de cancelamento: a **rejeição** de um
aprovador, o cancelamento pelo operador e — em um perfil que define `Approval.ExpiresAfter` — o fim do
prazo de espera. As três significam "este arquivo não será assinado, deliberadamente"; é a trilha de
auditoria que as distingue.
- **Uma rejeição é um veto, e ela devolve o arquivo.** Uma única rejeição interrompe o job, seja qual
for a conta do quórum. O job termina `Canceled`, o arquivo é devolvido a `output/` como
`.reject` — `folha.rem` vira `folha.reject.rem`, criptografado como qualquer outro artefato
quando o perfil criptografa — e o original é removido de `input/`. O arquivo devolvido **não está
assinado**: qualquer processo que leia `output/` como uma pasta de arquivos assinados precisa olhar o
nome. Se esse nome já estiver ocupado em `output/`, nada é sobrescrito: a cópia preparada vai para
`error//` e a entrada permanece em `input/`. A nova tentativa não se aplica — o arquivo é
corrigido e enviado de novo. Veja [A rejeição é um veto](https://docs.lacunasoftware.com/articles/bulk-signer/approvals#a-rejeição-é-um-veto).
- **A liberação volta para a fila comum** em vez de retomar o job de onde parou, de modo que um job
liberado passa pela mesma reivindicação e pelas mesmas etapas anteriores à assinatura que qualquer
outro — inclusive a
[verificação de datas de pagamento vencidas](https://docs.lacunasoftware.com/articles/bulk-signer/cnab240#datas-de-pagamento-que-já-passaram), que é
exatamente a verificação que precisa ser refeita depois de uma espera humana sem prazo definido (a
menos que o perfil desligue essa verificação com `CheckCnab240PaymentDates`, para um banco que processa
um pagamento com data passada no próximo dia útil — veja [Arquivos de pagamento CNAB240](https://docs.lacunasoftware.com/articles/bulk-signer/cnab240)).
O job é retomado sobre a mesma cópia com que ficou retido, e o hash dos bytes da cópia preparada é recalculado
imediatamente antes de a assinatura ser gerada; uma divergência faz o job falhar com
`approval.content-changed`. Veja [Aprovações](https://docs.lacunasoftware.com/articles/bulk-signer/approvals#o-que-é-aprovado).
- **Um job retido também pode expirar, se o perfil assim definir.** Com `Approval.ExpiresAfter`
definido, um job retido além da janela é cancelado com o motivo `Approval window expired.`, sua cópia
preparada é movida para `error/` e um evento operacional `ApprovalExpired` é registrado. A janela é
contada em tempo de relógio: **uma pausa não a estende**. Veja
[O prazo de espera](https://docs.lacunasoftware.com/articles/bulk-signer/approvals#o-prazo-de-espera).
- **O cancelamento só é válido a partir de `Queued`, `AwaitingSigner` ou `AwaitingApproval`.** Jobs
locais em andamento (`Processing`, `Verifying`) não podem ser cancelados — eles rodam até concluir ou
falhar naturalmente. O endpoint de cancelamento retorna `409` com `code = "job.not-queued"` para um job
local em andamento. Em perfis LacunaSigner, cancelar um job `AwaitingSigner` também faz uma chamada de
cancelamento remoto, em melhor esforço, *depois* que a transição local para `Canceled` é confirmada —
uma falha remota **não** desfaz o cancelamento local. Veja
[Semântica do cancelamento](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#semântica-do-cancelamento).
- **`Canceled` é terminal.** Os arquivos de jobs cancelados permanecem em `input/`; o observador respeita
os cancelamentos recentes e não os reenfileira automaticamente. As ações do operador (envio de
arquivos, nova tentativa, nova varredura) reenfileiram.
- **`Failed` é terminal, e o arquivo espera da mesma forma.** Uma falha deixa a entrada onde estava, e
desde a 2.11.0 o observador não a enfileira de novo por conta própria — nem no próximo tique de uma
pasta com polling, nem na enumeração feita na inicialização do serviço — até que um operador a
reexecute por nova tentativa, nova varredura ou envio. (Antes da 2.11.0, uma pasta com polling — toda
pasta do Azure Files e uma pasta local configurada para polling — oferecia de novo, a cada tique, um
arquivo que tinha falhado, de modo que uma falha cuja causa persistia produzia um novo job `Failed` por
tique; logo depois de um **Limpar Jobs**, parecia que a limpeza não tinha funcionado.) A decisão é
tomada apenas pelo status do job mais recente; por isso, um arquivo corrigido deixado com o mesmo nome
é capturado por essas mesmas ações do operador, e não pelo observador.
- **`Failed → Queued` não é uma transição — é um novo job.** A nova tentativa cria um job novo com
`ParentJobId = (o job falho).Id`, copiando a entrada original. O job que falhou permanece
`Failed` para sempre, para fins de auditoria. Um job que assina um documento do Lacuna Signer também
aceita nova tentativa a partir de `Canceled`, da mesma forma: um job novo para o mesmo documento e a
mesma ação do fluxo, que relê o documento quando é reivindicado.
## Quando um arquivo de entrada muda no meio de um job
Um produtor às vezes reenvia um arquivo com o mesmo nome enquanto o Bulk Signer ainda está trabalhando
no anterior — um valor corrigido, um lote reexportado, uma nova tentativa do ERP. Quando isso acontece,
**a correção não é ingerida**: o observador vê que um job ativo já detém aquele caminho e recusa o
enfileiramento duplicado — a mesma regra que impede um arquivo de ser enfileirado duas vezes.
O que o pipeline faz nesse caso é se recusar a destruir a correção. Antes de apagar a entrada original,
o worker compara o arquivo com o que foi registrado enquanto ele era copiado para `processing/` —
sempre o tamanho e o SHA-256, e também a entity tag do serviço de armazenamento quando o arquivo está em
um compartilhamento. Se coincidirem, a entrada é apagada normalmente. Se não, **o arquivo é deixado
exatamente onde está** e a divergência é registrada em três lugares:
- um evento operacional `InputDiverged`, com o código `job.input-diverged`;
- uma entrada no histórico do próprio job, visível em `/jobs/{id}`, com o mesmo código;
- o contador `bulksigner_inputs_diverged_total{profile}`.
> **Observação: Uma divergência não é uma falha de assinatura**
>
> A assinatura é válida, o artefato está em `output/` e o job é concluído normalmente — o que foi
> assinado é o arquivo que foi copiado para processamento e, quando há etapa de aprovação, aprovado. Nada no job
> precisa ser corrigido.
**O arquivo reescrito é então devolvido à pasta monitorada e assinado como um job próprio.** O evento
de mudança do observador disparou *durante* a execução do job e foi corretamente descartado, e nenhum
outro evento chegará para um arquivo que simplesmente continua parado ali — por isso o pipeline devolve
o caminho explicitamente, **depois** que o job atinge um status terminal. O arquivo volta pela rota
*comum* de candidatos do observador, então o detector de estabilidade, as listas de ignorados da pasta e
o perfil dela se aplicam exatamente como a qualquer outra chegada.
Dois casos ainda exigem sua intervenção. A devolução é descartada, com um aviso no console, quando:
- **O job não veio de uma pasta monitorada** — um upload via REST não tem um observador responsável pelo
caminho. Envie o arquivo de novo se ele deve ser assinado.
- **Nenhum observador está rodando para aquela pasta** — ou o processo ainda está subindo (o que se
resolve instantes depois), ou o observador da pasta parou após falhas repetidas. Confira a página
Pastas de entrada; uma nova varredura ingere o conteúdo da pasta assim que o problema de origem for
corrigido.
**O que verificar quando você encontra uma divergência:**
1. **A correção deveria substituir algo já assinado?** A primeira assinatura cobre o conteúdo
substituído, e ela é válida; se um sistema a jusante não deve agir com base nela, essa é uma decisão
de negócio a ser tomada explicitamente. Observe que o segundo artefato recebe o nome a partir do nome
do arquivo de entrada e, portanto, tem nome idêntico ao primeiro: se você ainda não retirou o
primeiro de `output/`, o segundo job falha na promoção com
`Output already exists at … resolve manually before re-queueing`. Mova ou retire o primeiro e então
faça uma nova tentativa do job.
2. **O produtor reenvia arquivos rotineiramente?** Uma contagem que acompanha a taxa de jobs retidos
indica que arquivos estão sendo reexportados durante janelas de aprovação, e cada um custa uma
assinatura duplicada e uma segunda passagem pela etapa de aprovação. A correção é do lado do
produtor — grave cada remessa com um nome único.
3. **O arquivo estava apenas ilegível ou bloqueado?** Um arquivo que o próprio produtor mantém aberto
para escrita é tentado novamente algumas vezes e então informado como divergência (`unreadable: …`).
O mesmo vale para um arquivo sobre o qual outro processo obteve um bloqueio exclusivo
(`held by another lease: …`). Nos dois casos nada é forçado. Um **bloqueio** que nunca é liberado
geralmente significa que uma segunda instância do Bulk Signer está monitorando a mesma pasta — uma
configuração a corrigir, e não um produtor por quem esperar.
A janela que isso fecha é maior nos fluxos que colocam uma pessoa no circuito. Um job local comum faz o
stage e apaga a entrada com segundos de diferença; um job em `AwaitingApproval` sem `ExpiresAfter`
espera indefinidamente.
### Os dois bloqueios de um arquivo de entrada
O Bulk Signer obtém um bloqueio exclusivo sobre um arquivo da sua pasta de entrada **duas vezes, por
pouco tempo, e nunca no intervalo entre elas**:
1. **Enquanto copia o arquivo para processamento.** Obtido quando o pipeline se compromete a copiar e liberado
assim que a cópia termina. Enquanto ele vale, nada pode escrever no arquivo entre a leitura que o
copia e a leitura do identificador que depois servirá para reconhecê-lo.
2. **Enquanto apaga o arquivo.** Um bloqueio separado, para que, em um compartilhamento, a comparação e
a exclusão sejam uma única operação.
**Nada bloqueia o seu arquivo enquanto um job espera por uma pessoa.** Um job retido em
`AwaitingApproval` ou `AwaitingSigner` mantém um bloqueio exclusivo sobre a própria cópia preparada em
`processing/`, por todo o tempo que a espera durar — mas não sobre o arquivo na sua pasta de entrada,
porque um quórum pode levar dias e o seu ERP grava nessa pasta.
**Um bloqueio nunca é quebrado e um arquivo nunca é apagado à força.** Se outro processo detém o seu
arquivo de entrada quando o Bulk Signer quer preparar a cópia, o job **falha** com uma mensagem que
cita o arquivo. Se outro processo o detém no momento da exclusão, a exclusão é adiada, tentada de novo
e então informada como divergência.
> **Info: O valor de um bloqueio depende de onde a pasta está**
>
> Em uma pasta de entrada no **Azure Files**, o bloqueio é um lease real do lado do serviço: ele impede
> escritas e exclusões por qualquer outro cliente daquele compartilhamento, inclusive outra instância do
> Bulk Signer. Em uma pasta de entrada **local**, ele é um controle interno do próprio Bulk Signer e não
> impede nada fora deste processo — um sistema de arquivos não consegue expressar "negue escritas a todos,
> mas permita a minha própria exclusão". O que protege uma entrada local é a comparação, e não o bloqueio,
> e **a comparação é igualmente forte nos dois casos**.
## O que muda no dia a dia em um compartilhamento
`Storage:Provider = AzureFiles`, ou uma única pasta de entrada que o indique, muda quatro coisas. Pausa,
cancelamento, nova tentativa, nova varredura, o botão de download, a máquina de estados do job, a etapa
de aprovação, a criptografia e o que um aprovador vê se comportam de forma idêntica — esta
funcionalidade move bytes e nada mais.
**1. A entrada depende de um temporizador, então deixa de ser quase instantânea.** Uma pasta local é
orientada a eventos: o sistema operacional informa um arquivo novo em milissegundos. O Azure Files não
publica notificações de mudança, então uma pasta remota é **enumerada a cada intervalo de polling**. O
pior caso, do momento em que o produtor fecha um arquivo até um job aparecer em `Queued`, é o intervalo
de polling (30 s por padrão) mais a janela de estabilidade mais uma ida e volta — **cerca de meio minuto
com os valores padrão**, e até um intervalo inteiro em um tique desfavorável.
- O intervalo é por pasta, então uma pasta de folha de pagamento pode fazer polling a cada 10 s enquanto
uma pasta de arquivo morto faz a cada 5 minutos.
- O mínimo é 5 s, e o preço é em dinheiro: todo tique é uma transação de listagem, tenha chegado algo ou
não. Uma pasta com polling a cada 5 s custa seis vezes o que a mesma pasta custa a cada 30 s, ociosa
ou não.
- Dois caminhos **não** dependem do temporizador e continuam imediatos: um upload (`POST /api/files`,
ou **Enviar arquivos** na página Jobs) e `POST /api/rescan`. Se alguém precisa de um arquivo assinado
*agora*, faça uma nova varredura daquela pasta em vez de reduzir o intervalo para sempre.
Não interprete um primeiro job lento como uma pasta com defeito. Consulte a página Pastas de entrada:
uma pasta que está `Running`, sem erro e com uma varredura recente está funcionando exatamente assim.
**2. Uma pasta sem movimento e uma pasta inacessível parecem idênticas vistas do compartilhamento,
então consulte os indicadores.** Uma pasta que não pode ser listada, não pode ser aberta ou cuja
credencial foi recusada aparece na página Pastas de entrada, em `GET /api/folders` (`status`,
`lastError`) e em `GET /api/ready` — ela nunca é informada como uma pasta que simplesmente não tem nada
novo. **O indicador que merece alerta é o `/api/ready`**: sem ele, uma pasta degradada pode passar
despercebida por todo o tempo em que ninguém abrir o dashboard, e arquivos de pagamento se acumulando
sem assinatura pedem um telefonema, e não uma simples notificação.
**3. Inspecionar arquivos exige um cliente de armazenamento, não um shell.** `error//`,
`processing//` e `output/` ficam no compartilhamento; então, sempre que esta documentação disser
"olhe o arquivo em `error/`", entenda o Azure Storage Explorer, o `az storage file download` ou uma
montagem na sua própria estação de trabalho. A cópia preparada de um job ativo tem um lease infinito,
então ela recusa escritas e exclusões vindas de qualquer lugar, inclusive das suas próprias ferramentas.
`logs/` e o banco SQLite **não** ficam no compartilhamento e nunca podem ficar.
**4. O compartilhamento é marcado, e a marca é lida no boot.** Veja a próxima seção.
## Quando outra instância parece ser dona do compartilhamento de trabalho
**Esta seção se aplica apenas quando `Storage:Provider = AzureFiles`.** Uma árvore de trabalho local não
é armazenamento compartilhado — duas instâncias apontadas para o `data/` de um mesmo host são a mesma
instância duas vezes. Implantações locais não têm marcador, nem linha no banner, nem aviso.
> **Observação: Esta seção inteira descreve o modo cluster desligado**
>
> Com `Cluster:Enabled = true`, o marcador significa outra coisa: o compartilhamento é reivindicado pelo
> *cluster*, e não por uma instância, as irmãs o compartilham deliberadamente, e a linha
> `work share owner` mostra `this cluster (one marker, shared between instances)`. Com a chave ligada, o
> que o marcador protege é a única catástrofe descrita abaixo que banco de dados nenhum consegue
> enxergar — dois bancos de dados operacionais sobre um mesmo compartilhamento —, e uma instância cujo banco não
> corresponde ao marcador **se recusa a iniciar**. Veja
> [Alta disponibilidade](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability#a-verificação-do-compartilhamento-de-trabalho-cobre-menos-do-que-a-catástrofe-que-motivou-sua-criação).
Um compartilhamento de trabalho é armazenamento compartilhado, o que leva a supor que dois hosts agora
podem atender a uma mesma implantação. **Fora do modo cluster, não podem** — e mover o banco
operacional para o SQL Server não muda isso por si só, porque nenhum dos impedimentos está no banco:
- a **flag de pausa do pipeline é uma linha única**, lida a cada iteração de polling pelo worker (que
se supõe único), então dois workers leem a mesma linha e ambos agem com base nela;
- os **observadores são por instância e orientados a eventos**, então os dois veem um arquivo chegar e
os dois o enfileiram, e o perdedor registra uma falha de enfileiramento naquela pasta;
- **nada registra qual instância é dona de um job**, então um boot varre linhas em que uma irmã ainda
está trabalhando.
O modo cluster é a resposta suportada para esses três pontos, e ele precisa ser ativado deliberadamente,
em vez de ser deduzido do provider de armazenamento — veja [Azure App Service (modo cluster)](https://docs.lacunasoftware.com/articles/bulk-signer/azure).
**Como a marca funciona.** A instância obtém um lease exclusivo e sem expiração sobre o
`bulksigner-instance.json`, um pequeno arquivo ao lado de `processing/`, `output/` e `error/`. Ele
registra o nome do host, o id do processo e o momento da reivindicação. Um desligamento normal libera o
lease; o arquivo permanece como registro de quem rodou por último.
**O que acontece quando o marcador já está detido por outro.** A inicialização nunca é bloqueada. Em vez
disso:
1. uma entrada `Critical` aparece no log com o **host e o id de processo** do detentor anterior;
2. a mesma linha é impressa na saída padrão, e a linha `work share owner` do banner mostra
`CONTENDED at startup by …`;
3. a página Sistema exibe o aviso acima dos caminhos de armazenamento;
4. o `/api/ready` retorna **503** com uma verificação `work-share-owner` vermelha, cujo detalhe em
`/api/ready/details` é a mesma frase;
5. o lease é quebrado e obtido, e o boot segue em frente.
**Por que um aviso, e não uma recusa.** Um lease fica no serviço de armazenamento, e não no processo que
o obteve — então uma queda, um `docker kill`, uma falta de energia ou um OOM deixam o marcador detido por
um processo que não existe mais. Recusar-se a iniciar transformaria cada um desses casos em uma
recuperação manual no meio da noite. Este produto não consegue distinguir um detentor morto de uma irmã
viva; por isso, ele entrega a você os dois fatos que permitem essa distinção e continua assinando.
**O que fazer quando você vê o aviso.** Verifique se o host e o processo indicados ainda estão rodando.
- **É este host, e aquele processo não existe mais.** A instância anterior não foi desligada
normalmente. Não há nada de errado agora.
- **É outro host, ou aquele processo está vivo.** Você tem duas instâncias em um mesmo compartilhamento
de trabalho. Pare uma delas e depois decida qual banco de dados é o de referência.
> **Observação: A linha de readiness não volta ao normal sozinha, e isso é deliberado**
>
> O marcador é reivindicado uma única vez, no boot; nada o relê, porque não há resposta mais atual a
> obter — esta instância o detém agora. Assim, uma parada abrupta custa um ciclo de readiness vermelho, e
> o boot depois de uma parada normal volta a ficar verde.
**O que de fato diverge.** Duas instâncias assinando a partir de um mesmo compartilhamento de trabalho
**não** assinam o mesmo arquivo duas vezes: o lease por arquivo sobre um arquivo de entrada é recusado,
e não quebrado. O que diverge é tudo o que está no banco de cada instância:
- **Estado das aprovações** — um job retido na etapa de aprovação só existe no banco de uma das
instâncias. A outra não sabe nada sobre ele, sobre os aprovadores dele nem sobre o quórum que ele
aguarda. É isso que exige ação rápida.
- **Estado de pausa** — o `POST /api/pipeline/pause` retém uma instância. A outra continua assinando.
- **Estatísticas e histórico de jobs** — cada instância informa os seus, então nenhum dos dashboards
mostra o quadro completo.
**Se o marcador não puder ser reivindicado de forma alguma** — um compartilhamento inacessível, uma
credencial trocada —, a linha mostra `not claimed cleanly at startup: …` e a readiness fica vermelha por
esse motivo, e não pelo outro. Nesse caso, não se sabe se outra instância o detém, e o desconhecido não
é informado como se fosse a resposta tranquilizadora.
## Quais instâncias estão vivas (somente no modo cluster)
Com `Cluster:Enabled = true`, cada instância mantém uma linha no banco operacional — quem ela é, quando
enviou o último heartbeat e qual versão da aplicação está rodando —, e toda instância consegue ler as
linhas de todas as outras. **Sistema → Instâncias** no dashboard mostra essa tabela.
| Coluna | O que ela informa |
|---|---|
| Instância | A identidade derivada. No App Service, ela vem do `WEBSITE_INSTANCE_ID` da plataforma, então é estável por toda a vida da instância e distinta entre irmãs. |
| Estado | **Viva** enquanto o último heartbeat está dentro de `Cluster:StaleAfterSeconds`; **Parada** quando o processo aposentou a própria linha em um desligamento limpo — o rastro normal de uma reimplantação; **Sem sinal** quando ficou em silêncio além do limite sem avisar. **Sem sinal** é uma presunção, não uma morte confirmada — veja [a suposição](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability#uma-morte-presumida-é-uma-suposição). |
| Versão | A versão da aplicação que aquela instância está rodando. Dois valores diferentes aqui, em qualquer momento fora de uma janela de implantação, caracterizam a condição de versões mistas, que é informada como um Critical no boot da instância mais nova. |
| Último sinal | Idade do heartbeat mais recente. A legenda abaixo da tabela indica a cadência (`Cluster:HeartbeatSeconds`, padrão 15) e o limite de obsolescência (padrão 60) efetivamente em vigor. |
Uma linha é destacada como a instância que respondeu à sua requisição. Como o balanceador de carga
escolhe a instância a cada requisição, recarregar a página move esse destaque entre as linhas — a
confirmação mais barata disponível de que o tráfego está de fato distribuído. Uma linha cuja instância
**deslocou** uma predecessora viva mostra, abaixo da identidade, qual predecessora foi deslocada e
quando — veja a próxima seção.
O `GET /api/folders` tem um campo `instance` pelo mesmo motivo: um cliente automatizado que o consulta
precisa distinguir "a pasta mudou" de "outra instância respondeu".
### Quando um boot encontra a própria identidade ainda viva
> **Aviso: Mudou na 2.5.0 — uma predecessora viva é deslocada, não recusada**
>
> Até a 2.4.x, uma instância que subia e encontrava a própria identidade ainda enviando heartbeats se
> recusava a iniciar (a 2.4.3 primeiro esperava por ela e depois recusava). Desde a 2.5.0, o novo boot
> **desloca** a detentora viva e segue em frente.
É assim que funciona uma reimplantação in-place no App Service: a plataforma inicia o novo container ao
lado do antigo, sob o mesmo id de instância, e mantém o antigo atendendo — e enviando heartbeats — até
que o novo passe no probe de aquecimento. Nem uma recusa nem uma espera dariam conta disso; então:
- **O novo boot** assume a identidade de imediato e registra um `Warning` com a encarnação deslocada, a
build dela e o último heartbeat. Não há falha de inicialização.
- **O processo deslocado se retira** no próximo heartbeat: um `Critical` no log *dele*, um evento
operacional `InstanceStoodDown`, uma linha `cluster-instance` vermelha no `/api/ready` dele — que
**não** reprova o probe, já que um 503 faria a plataforma retirar o único container para o qual ainda
está roteando — e um banner acima da tabela Instâncias na página Sistema dele. Ele não reivindica
nenhum job novo, não executa assunções e não faz polling de nada no Lacuna Signer; o que ele detém
roda até o fim, e ele continua atendendo a web até que a plataforma o pare.
- **Ele retoma o trabalho por conta própria** se a linha da encarnação mais nova depois ficar parada ou
sem sinal — uma recém-chegada que aposentou a própria linha em uma parada normal, ou um segundo host
parado depois — com um `Warning`, um evento `InstanceResumed` e o `/api/ready` verde de novo. Ele nunca
toma a identidade de volta de uma detentora que ainda envia heartbeats.
- **O que a vida deslocada deixou por terminar** é ignorado pela
[recuperação na inicialização](https://docs.lacunasoftware.com/articles/bulk-signer/operations#recuperação-na-inicialização) do novo boot e assumido um
`Cluster:StaleAfterSeconds` depois do deslocamento, sob a política normal de
[assunção](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quando-uma-instância-para-de-responder-uma-sobrevivente-assume-seus-jobs).
- **Um boot cujo banco não respondeu** se registra no primeiro heartbeat que consegue chegar ao banco,
deslocando exatamente como o boot teria feito.
- **Um desligamento limpo aposenta a própria linha antes**, então um reinício depois de uma parada normal
não desloca nada — e é por isso que *parar, trocar, iniciar* continua sendo a implantação mais limpa.
> **Observação: No App Service, uma implantação que falha é desfeita voltando para a tag de imagem anterior**
>
> Se o novo container falha no aquecimento depois de deslocar o antigo, o App Service **não** volta para o
> container antigo: ele para o **site inteiro** — inclusive o container que se retirou, antes que a linha
> da sucessora ficasse sem sinal — e continua reiniciando-o com a imagem nova. A retomada descrita acima
> não tem como acontecer nesse caso. Aponte o app de volta para a tag de imagem anterior
> (`az webapp config container set`), e ele fica pronto de novo em cerca de dois minutos. Veja
> [Atualizações exigem parada total](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability#atualizações-exigem-parada-total).
**Dois hosts que se apresentam com o mesmo nome, portanto, também não são recusados**: eles se revezam,
com avisos nos dois lados, e exatamente um deles reivindica trabalho a cada momento. Se você vir um
deslocamento quando ninguém está reimplantando, leia os dois logs e renomeie um dos hosts ou aponte-o
para o seu próprio banco. A única recusa de boot que resta é a de um registro que perdeu todas as
disputas de escrita pela própria linha; ela informa a build e o último heartbeat da vencedora. Apagar a
linha enquanto uma detentora está rodando remove o aviso, e não a condição. Veja
[Diagnóstico de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#modo-cluster).
## Quando uma instância para de responder, uma sobrevivente assume seus jobs
Toda instância sobrevivente acompanha a tabela de heartbeat. Quando uma irmã fica sem sinal, uma
sobrevivente reivindica as linhas em andamento dela e reconcilia cada uma **conforme o ponto a que ela
havia chegado**, e não repetindo-a:
| O job da instância morta estava… | O que a sobrevivente faz | Por quê |
|---|---|---|
| Reivindicado, mas não havia chegado à chamada de assinatura | **Reenfileira** | Nada foi tentado, então nada está sendo repetido. |
| Depois da chamada de assinatura | **Marca como falho**, por cautela | Uma assinatura nunca é tentada de novo sem que uma pessoa decida isso. `Failed` é um desfecho terminal honesto, não "travado" — a [nova tentativa manual](https://docs.lacunasoftware.com/articles/bulk-signer/operations#repetindo-jobs-que-falharam) do operador continua sendo a forma de repetir. |
| `AwaitingSigner` (despachado ao Lacuna Signer) | **Reatribui** à sobrevivente, que retoma o polling | O lado remoto detém o trabalho; só o polling precisa de um novo dono. |
| Assinando um documento do Lacuna Signer, depois da chamada de assinatura (`Processing` ou `Verifying`) | **Pergunta ao Lacuna Signer**, e conclui, cancela ou marca como falho conforme a resposta | Um job assim não tem arquivo, então nada em `output/` pode responder. Veja [Quando a execução que o assinava termina](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#quando-a-execução-que-o-assinava-termina). Antes da chamada de assinatura, ele é reenfileirado como qualquer outro. |
Cada assunção grava um evento operacional `JobTakenOver` com **as duas** instâncias, de modo que a
trilha de auditoria registra quem perdeu o trabalho e quem o assumiu.
> **Aviso: A assunção fica sujeita à pausa**
>
> O `POST /api/pipeline/pause` retém todas as instâncias, e a assunção não roda enquanto o pipeline está
> pausado. Isso é deliberado: um operador que pausa um cluster para investigar um banco que ficou lento é
> exatamente a pessoa que não pode ver todas as instâncias declarando mortas todas as irmãs.
Há dois tipos de linha que nada jamais assumirá, e ambos são informados em vez de adotados:
- **Um job sem dono nenhum**, deixado por uma build anterior à coluna de dono ou por uma execução com o
modo desligado. A solução é indicada em todos os lugares que encontram um desses — suba uma vez com
`Cluster:Enabled = false`, para que a [recuperação na inicialização](https://docs.lacunasoftware.com/articles/bulk-signer/operations#recuperação-na-inicialização)
normal o varra, e então religue o modo.
- **Um job detido por uma instância nomeada que não tem linha de heartbeat.** A ausência de heartbeat não
é prova de morte, então isso é informado uma vez e deixado como está, em vez de ser interpretado como
permissão para fazer falhar um trabalho vivo.
Os dois casos, e o motivo pelo qual adotá-los reintroduziria o defeito que a funcionalidade elimina,
estão em
[Alta disponibilidade](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability#linhas-que-ninguém-possui-não-são-reconciliadas-por-ninguém).
## Contenção entre instâncias não é uma falha
Toda instância monitora todas as pastas de entrada, então a cada chegada elas disputam o arquivo. Esse é
o projeto, e o lado perdedor da disputa é classificado como um **desfecho esperado**, e não como um
erro:
- O enfileiramento perdedor é recusado por um índice único parcial sobre os caminhos originais ativos e
respondido como `AlreadyActive`. Cada arquivo vira exatamente um job.
- Um conflito de lease em um arquivo de entrada é registrado no nível de desfecho esperado, com um id de
evento próprio, de modo que "uma irmã chegou primeiro" e "outro componente desta instância chegou
primeiro" continuem sendo fatos diferentes.
- **Nenhum dos dois conta para o limite de falhas consecutivas da pasta**, e um desfecho `AlreadyActive`
zera esse contador exatamente como um enfileiramento bem-sucedido. Um cluster movimentado, portanto,
não consegue disparar o [disjuntor por pasta](https://docs.lacunasoftware.com/articles/bulk-signer/operations#isolamento-de-falhas-do-observador-por-pasta) só por
estar movimentado.
A reivindicação em lote também se degrada sob contenção — ela passa a reivindicar uma linha por vez e
registra a disputa perdida. É um custo pequeno e conhecido, e não uma falha.
## O pipeline de assinatura
```text
input/file.pdf
│ Observador (ou POST /api/files)
▼
Queued ──▶ worker reivindica ──▶ move input → processing/ ──▶ Assina ──▶ Verifica
│
Encryption.Enabled? ────────────┤
sim → output/file.signed.pdf.enc
não → output/file.signed.pdf
em caso de falha → error/
```
O worker é de instância única por conjunto de pastas configurado e processa até
`Pipeline:MaxConcurrency` jobs em paralelo. O padrão `1` é sequencial; os operadores podem optar por
`N > 1` para ganhar vazão (somente com PFX — veja a ressalva sobre PKCS#11 / WindowsStore em
[Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates)). O worker:
1. Consulta a fila a cada `Pipeline:PollIntervalSeconds` segundos, limitado pela concorrência
configurada. Quando todos os slots estão ocupados, o polling fica suspenso até um slot ser liberado.
2. Verifica a flag de pausa. Quando pausado, o worker gira em vazio sem pegar trabalho; os jobs que já
estão em andamento seguem até a conclusão natural. A flag de pausa é lida a cada iteração de polling
e sobrevive a reinicializações.
3. Reivindica o próximo job `Queued` de forma atômica (transição `Queued → Processing`). Se uma escrita
concorrente (um cancelamento ou outro worker) alterou a linha antes, o worker passa para a próxima
iteração.
4. Para cada job reivindicado, move a entrada para `processing//`, assina, verifica,
opcionalmente criptografa e então promove para `output/`. Cada job roda isolado, com a própria pasta
de processamento.
5. Em qualquer falha: move o conteúdo de `processing//` para `error//`, marca o job como
`Failed` e registra a mensagem da exceção no campo de erro do job e no histórico.
6. **A entrada original só é removida de `input/` depois de uma verificação bem-sucedida, e somente se
ainda for o arquivo que foi copiado para processamento.** A verificação acontece antes da exclusão, nunca o
contrário — e um arquivo que o pipeline não processou nunca é apagado. Veja
[Quando um arquivo de entrada muda no meio de um job](https://docs.lacunasoftware.com/articles/bulk-signer/operations#quando-um-arquivo-de-entrada-muda-no-meio-de-um-job).
**Drenagem na pausa.** Quando um operador pausa com jobs em andamento, o worker para de reivindicar
novos, mas os que já estão rodando vão até o fim. O card "Slots ocupados" do dashboard vai diminuindo
conforme eles terminam.
**Três portas de entrada.** Um arquivo chega à fila por uma pasta monitorada, pelo `POST /api/files` ou
pelo botão **Enviar arquivos** da página Jobs, que envia cada arquivo pelo mesmo handler da rota REST —
o mesmo limite de tamanho, a mesma sanitização do nome do arquivo e as mesmas verificações de perfil —,
de modo que os dois recusam um arquivo exatamente nas mesmas condições. O diálogo pede um perfil de
assinatura habilitado e termina com um relatório por arquivo, com um link para cada job criado.
`Upload:Enabled = false` desliga **os dois** caminhos de upload de uma vez: o `POST /api/files` responde
`409` com `upload.disabled`, e a página Jobs não mostra o botão de upload. Pastas monitoradas, nova
varredura e nova tentativa não são afetadas; a chave é lida no boot, então religá-la exige um reinício.
Veja [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#upload).
### Perfis LacunaSigner — worker de polling separado
Quando um perfil usa `Method = LacunaSigner`, o worker apenas **despacha** o job ao Lacuna Signer
(upload + criação do documento) e imediatamente o passa para `AwaitingSigner` — o slot de concorrência
é liberado assim que o despacho é concluído com sucesso. Um worker de polling separado percorre todas as
linhas `AwaitingSigner` na própria cadência (`Signer:PollIntervalSeconds`, padrão 30 s), baixa os bytes
quando o documento remoto é concluído e executa a mesma etapa final de verificar → opcionalmente
criptografar → promover. Veja [Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer).
## Roteando uma pasta monitorada para um perfil de assinatura
> **Aviso: Mudou na 2.2.0 — o perfil escolhe a sua pasta**
>
> Uma pasta monitorada é assinada com **o perfil que a escolheu**, e um perfil escolhe a sua pasta na
> própria página no dashboard — uma pasta por perfil, um perfil por pasta. O `Storage:Inputs[].Profile`
> passou a ser **dado de seed (carga inicial)**: ele é lido uma única vez, no primeiro boot com a tabela
> de perfis vazia, e ignorado (com aviso de que foi ignorado) em todos os boots seguintes. Depois do
> primeiro boot, o arquivo de configuração não consegue rotear uma pasta. Veja
> [`Storage:Inputs[].Profile`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#storageinputsprofile--roteamento-por-pasta).
Nada mais sobre a pasta muda de lugar: o nome, o caminho, o provider, as credenciais e o intervalo de
polling continuam em `Storage:Inputs[]`, são validados no boot e são o que a página Pastas de entrada
lista. O que a página do perfil decide é qual pasta alimenta qual perfil — ou seja, qual certificado e
qual regra de aprovação os arquivos da pasta recebem.
### Pela página do perfil
1. Abra a página do perfil no dashboard (`/profiles/{name}`) e clique em **Editar comportamento**. O
seletor **Pasta de entrada** oferece *nenhuma*, todas as pastas que este host configurou e que nenhum
outro perfil usa, e a pasta atual do próprio perfil.
2. Escolha a pasta e salve. O observador da pasta começa a monitorá-la em um ou dois intervalos de
`Pipeline:PollIntervalSeconds`, em todas as instâncias, sem reinício. Os arquivos que já estão na
pasta são capturados sem nova varredura.
3. Confira a página Pastas de entrada: o card da pasta agora mostra o chip do perfil, com um link de
volta para ele.
Um perfil criado em `/profiles/_new` escolhe a sua pasta no mesmo formulário. O evento de auditoria
registra a mudança pelo nome da pasta, nunca pelo caminho — `InputFolder (none) → remessas` em uma
edição e `Input folder: remessas.` em uma criação.
**Duas recusas, ambas ao salvar.** Uma pasta que este host não configurou em `Storage:Inputs[]`, e uma
pasta que outro perfil já usa — nesse segundo caso, a mensagem informa o dono; limpe a pasta nele
primeiro ou escolha outra. Dois salvamentos que escolhem a mesma pasta no mesmo instante deixam
exatamente um dono, e o perdedor é informado de quem ficou com ela. Nenhuma das recusas acontece no
boot: um vínculo armazenado não é revalidado; então, uma pasta renomeada ou removida da configuração
depois que um perfil a escolheu gera um **aviso de degradação** — no banner de inicialização, como uma
linha `profile-input-folder:` no `/api/ready` que não reprova o veredito, e como um alerta em
`/profiles` e na própria página do perfil —, enquanto o perfil continua atendendo uploads.
### O que significa *não atribuída*
Uma pasta que nenhum perfil escolheu está **não atribuída**, e nada a monitora. Ela aparece como:
- um chip cinza na página Pastas de entrada com o texto `sem perfil — nenhum perfil escolheu esta pasta`;
- `status: "Unassigned"`, sem `profileName`, no `GET /api/folders`;
- uma linha `input-folder:` **verde** no `/api/ready` — nada está quebrado, e uma linha vermelha
mandaria um orquestrador retirar uma instância por causa de uma pasta que ninguém pediu para ela
monitorar;
- um `Warning` no log, no boot e sempre que um perfil libera a pasta.
Os arquivos deixados em uma pasta não atribuída **esperam**: não são ignorados, nem movidos, nem
recusados, e são capturados no momento em que um perfil escolhe a pasta. Uma pasta não atribuída **não**
passa a usar o `default` — isso colocaria os arquivos dela sob uma regra que ninguém escolheu. O
`default` é o perfil usado por um upload que não indica perfil, e por uma pasta que não indica nenhum na
única leitura do seed; nunca por uma pasta que ficou sem perfil depois disso.
Uma pasta fica não atribuída de uma de três formas: o seed a deixou assim no primeiro boot (uma pasta
que indica um perfil que a seção não declara, ou uma segunda pasta que indica um perfil que uma pasta
anterior já tomou), um perfil a liberou na própria página, ou a tabela de perfis foi criada pelo seed de
uma versão anterior à 2.2.0 e a pasta nunca foi vinculada. A solução é a mesma em todos os casos:
escolha a pasta na página de um perfil.
### Movendo uma pasta entre perfis
Limpe a pasta no perfil que a tem e **depois** escolha-a no perfil que deve tê-la — nessa ordem, porque o
segundo salvamento é recusado enquanto o primeiro perfil ainda é o dono. Nesse intervalo, a pasta fica
brevemente não atribuída; um arquivo que chegar nessa janela é capturado assim que o segundo salvamento
entrar em vigor, então nada se perde e nada é assinado duas vezes. Os jobs já enfileirados a partir da
pasta mantêm o perfil com que foram enfileirados, de modo que o nome de uma pasta em um job sempre
corresponde a exatamente um certificado e uma regra de aprovação.
Duas pastas que devem seguir a mesma regra são dois perfis com as mesmas configurações.
### Desabilitando um perfil que usa uma pasta
Desligar **aceitar novos trabalhos** é recusado, com o nome da pasta, enquanto o formulário ainda tiver
uma pasta — a alternativa seria uma pasta cujos arquivos deixam de ser assinados silenciosamente. Limpe
a pasta no mesmo salvamento e a desabilitação é aceita; a pasta fica não atribuída, isso aparece na
página Pastas de entrada, e ela espera por outro perfil.
### O que uma nova varredura e uma nova tentativa fazem com o vínculo
- Uma **nova varredura** pula inteiramente uma pasta não atribuída e informa isso — veja
[Nova varredura](https://docs.lacunasoftware.com/articles/bulk-signer/operations#nova-varredura).
- Uma **nova tentativa mantém o perfil que o job que falhou registrou**, seja qual for o perfil que a
pasta alimenta hoje — veja [Repetindo jobs que falharam](https://docs.lacunasoftware.com/articles/bulk-signer/operations#repetindo-jobs-que-falharam). Um job que
deve ser assinado com o novo perfil da pasta é cancelado e enviado de novo.
## Pausar e retomar
```bash
# Retém o worker (idempotente — já pausado também retorna 200)
curl -X POST http://localhost:8080/api/pipeline/pause \
-H "X-API-Key: $BULK_SIGNER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason":"Manutenção trimestral"}'
# Retoma (também idempotente)
curl -X POST http://localhost:8080/api/pipeline/resume \
-H "X-API-Key: $BULK_SIGNER_API_KEY"
# Inspeciona o estado atual
curl http://localhost:8080/api/pipeline/state \
-H "X-API-Key: $BULK_SIGNER_API_KEY"
```
Pausar e retomar são operações duráveis — a flag de pausa sobrevive a um reinício do serviço. Um worker
pausado continua aceitando uploads e capturas do observador (os jobs vão para `Queued`); eles apenas não
avançam. Os operadores veem "Pipeline: pausado" na página Sistema do dashboard.
Enquanto uma pausa está em vigor:
- Os jobs que já estão em `Processing` / `Verifying` terminam normalmente. A pausa impede a **próxima**
captura, não o trabalho em andamento.
- O gauge `bulksigner_pipeline_paused` passa a `1`.
- Um evento operacional é gravado com o `reason` opcional:
`"Pipeline paused by operator. Reason: Manutenção trimestral."`. A mesma convenção vale para a
retomada. Os dois eventos podem ser consultados na página `/events` do dashboard.
Uma pausa e uma retomada emitidas no mesmo instante não se sobrescrevem silenciosamente: exatamente uma
das duas escritas vence, e a perdedora recebe `409` com o código `pipeline.race-lost`, sem ter registrado
nada. Consulte de novo o `GET /api/pipeline/state` e repita a operação se a sua intenção continuar
valendo.
> **Observação: Implantações com SQL Server antes da 2.4.3**
>
> O banco operacional em SQL Server era criado sem a linha de estado do pipeline em que fica a flag de
> pausa; por isso, nessas versões, o `POST /api/pipeline/pause` respondia `pipeline.state-missing` e o
> pipeline continuava rodando. Desde a 2.4.3, uma migração aplicada no boot acrescenta a linha, e pausar e
> retomar funcionam nos dois providers.
## Cancelando jobs
```bash
curl -X POST http://localhost:8080/api/jobs/$JOB_ID/cancel \
-H "X-API-Key: $BULK_SIGNER_API_KEY"
```
Válido para `Queued`, `AwaitingSigner` e `AwaitingApproval`. Os dois estados de espera podem ser
cancelados justamente porque nada está ocupado com eles — um job `AwaitingSigner` espera por um serviço
remoto, um job `AwaitingApproval` espera por uma pessoa, e qualquer uma dessas esperas pode acabar sendo
uma que você não quer mais concluir. O endpoint retorna `409 { code: "job.not-queued" }` se o job já
passou desses estados (por exemplo, um job local que o worker pegou entre a decisão do operador e a
requisição). Jobs locais em andamento são intocáveis — removê-los no meio da assinatura deixaria
conteúdo órfão em `processing/` e uma saída não verificada.
- **`AwaitingSigner`:** a transição local para `Canceled` é confirmada primeiro, e então o documento
remoto no Lacuna Signer é cancelado em melhor esforço; uma falha remota é registrada no log e **não**
desfaz o cancelamento local. Veja [Semântica do cancelamento](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#semântica-do-cancelamento).
- **`AwaitingApproval`:** depois que o cancelamento é confirmado, a cópia preparada do job é movida de
`processing//` para `error//`, também em melhor esforço. O snapshot de aprovação do job é
**mantido** — ele registra a regra que o job aguardava, que é justamente o que uma auditoria pergunta
depois.
No dashboard, o botão **Cancelar** da página do job pede confirmação antes: o diálogo mostra o nome do arquivo,
explica o que o cancelamento faz a partir do status atual do job e lembra que um job cancelado não tem
**Tentar novamente** — o arquivo precisa de uma nova varredura ou de um novo envio para ser assinado de
novo. *Manter job* não cancela nada. A rota REST não mudou e não pede confirmação.
Depois do cancelamento:
- O job passa a `Canceled` (terminal).
- Uma entrada é acrescentada ao histórico de auditoria: `"Operator canceled: ."` (ou
`"Operator canceled."` se nenhum motivo foi informado).
- O arquivo permanece em `input/`. A memória de cancelamentos recentes do observador impede que ele seja
reenfileirado automaticamente; reexecuções feitas pelo operador por envio, nova tentativa ou nova
varredura reenfileiram o arquivo.
## Repetindo jobs que falharam
```bash
curl -X POST http://localhost:8080/api/jobs/$JOB_ID/retry \
-H "X-API-Key: $BULK_SIGNER_API_KEY"
```
Cria um novo job com um `Id` novo, os mesmos `FileName` / `OriginalPath` / `Format`,
`ParentJobId = (o job falho).Id` e estado inicial `Queued`. O job que falhou permanece `Failed`; a
cadeia pode ser reconstruída a partir do `ParentJobId`.
**A nova tentativa é assinada com o perfil que o job que falhou registrou**, e não com o perfil que a
pasta dele alimenta hoje: uma nova tentativa significa "assine do jeito que seria assinado", e seguir o
vínculo atual da pasta assinaria com uma regra que o job nunca teve. Um arquivo que deve passar para o
novo perfil da pasta é cancelado e enviado de novo. (Um job de antes da existência dos perfis de
assinatura não registrou nome e é repetido com o `default`.)
Um job que assina um documento do Lacuna Signer (`SignerFolder`) aceita nova tentativa a partir de
`Failed` **ou de `Canceled`**, e a nova tentativa recomeça do zero: a reivindicação relê o documento no
Signer e só assina enquanto a ação do titular ainda está pendente — veja
[Tentar novamente é o único caminho de volta](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#tentar-novamente-é-o-único-caminho-de-volta).
Retorna `404 { code: "job.not-found" }` para ids desconhecidos, `409 { code: "job.not-failed" }` para
jobs que não estão `Failed`, `409 { code: "job.input-missing" }` se o arquivo de entrada original não
está mais em disco, e duas recusas que são decisões, e não falhas — nos dois casos, o botão **Tentar
novamente** não aparece na página do job:
- `409 { code: "job.rejected-not-retriable" }` para um job que terminou `Failed` com
`approval.rejected`, porque a rejeição de um aprovador chegou depois que um worker já o tinha
reivindicado. O arquivo rejeitado já foi devolvido a `output/` com o nome `.reject`, e a entrada foi
removida, então uma nova tentativa só poderia falhar. Corrija o arquivo e envie-o de novo. (Uma
rejeição comum termina em `Canceled`, estado ao qual a nova tentativa também não se aplica.)
- `409 { code: "file.already-processed" }` para um job recusado porque outro job já tem o mesmo nome de
arquivo. A nova tentativa é isenta dessa regra, então repetir essa falha específica assinaria
justamente o arquivo que a regra recusou. Em vez disso, exclua o job que detém o nome — veja
[Nomes de arquivo já processados](https://docs.lacunasoftware.com/articles/bulk-signer/operations#nomes-de-arquivo-já-processados).
A página de detalhe do job no dashboard mostra links para o job de origem e para os jobs derivados, para
que os operadores possam percorrer uma cadeia de novas tentativas até a falha original.
## Nomes de arquivo já processados
> **Aviso: Mudou na 2.13.0 — um nome que já foi assinado é recusado**
>
> Com `Pipeline:RejectAlreadyProcessedFileNames` ligado — o padrão —, um arquivo que chega com um nome que
> um job `Completed` ou ainda ativo já tem **nunca é assinado**. As versões anteriores o assinavam de
> novo. Defina a chave como `false` para manter o comportamento antigo. Veja
> [Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#pipeline).
A comparação vale para o host inteiro e não diferencia maiúsculas de minúsculas, porque todas as pastas
monitoradas, todos os perfis e todos os uploads gravam na mesma pasta `output/`.
- **Pasta monitorada ou nova varredura:** o arquivo vira um job que já nasce `Failed` com
`file.already-processed`, indicando o job que detém o nome, e os bytes são movidos para a pasta
`error//` do novo job, para que o arquivo não seja oferecido de novo. O console avisa arquivo
por arquivo, e um evento operacional `FileAlreadyProcessed` é gravado. Uma nova varredura conta esses
casos em um número separado, `alreadyProcessed`. Se o arquivo não puder ser movido (outro processo o
detém), nada é registrado e ele permanece na pasta; uma nova varredura o conta em `errors`.
- **Upload:** `409` com `file.already-processed`; nada é armazenado.
- **O que não reserva um nome:** um job `Failed` ou `Canceled`. Colocar o arquivo de novo na pasta
depois de uma falha é a forma de tentar outra vez.
**Para aceitar um nome de novo, exclua o job que o detém** em `/jobs` — veja
[Excluindo um job](https://docs.lacunasoftware.com/articles/bulk-signer/operations#excluindo-um-job). Depois que ele é excluído, um arquivo reenviado com esse nome é
capturado pelo observador sem nova varredura. Nada impõe a regra no banco de dados: duas instâncias em
um cluster podem aceitar o mesmo nome no mesmo instante, e é a recusa em sobrescrever um arquivo que já
está em `output/` que barra a segunda.
## Excluindo um job
Para remover **um** job — por exemplo, o que detém um nome de arquivo que você quer que volte a ser
aceito —, exclua-o em `/jobs`: uma linha por vez, com um diálogo de confirmação e um motivo opcional.
Não há rota REST para isso.
- **Um job que um worker está executando** (`Processing` / `Verifying`) não pode ser excluído. Um job que
não terminou (`Queued`, `AwaitingApproval`, `AwaitingSigner`) é cancelado primeiro, exatamente como um
cancelamento faria — inclusive o cancelamento em melhor esforço do documento remoto no Lacuna Signer —
e é registrado com o status que tinha (`'', Queued, canceled to delete it`).
- **O que é removido:** o job, o histórico, os tempos, o detalhamento CNAB240, o snapshot de aprovação e
as aprovações registradas; as pastas `processing/` e `error/