Aplicativo desktop para assinatura digital de documentos PDF segundo o padrão ICP-Brasil, desenvolvido pela Câmara Municipal de Jataí-GO para o SAPL/Interlegis, para o Brasil.
O AssinaLegis permite ao servidor público visualizar e assinar documentos PDF com certificado A1 (arquivo PFX/P12) ou A3 (token/smartcard) em dois fluxos complementares: no modo M1 (SAPL/API), os documentos são carregados da API e enviados automaticamente após a assinatura; no modo M2 (Local), os arquivos PDF são carregados do computador, assinados por etapas e salvos em pasta local.
- Para Usuários Finais — Download e Instalação
- Primeira Configuração
- Modos de Operação (M1 e M2)
- Funcionalidades
- Para Desenvolvedores — Pré-requisitos
- Como Compilar e Executar
- Gerando Instaladores Nativos
- Estrutura do Projeto
- Arquitetura e Tecnologias
- Detalhes Técnicos Avançados
- Licença
Os instaladores prontos para uso incluem a runtime Java embutida — não é necessário instalar o Java separadamente.
| Sistema | Arquivo | Como instalar |
|---|---|---|
| Linux (Ubuntu/Debian) | assinalegis_*.deb |
sudo dpkg -i assinalegis_*.deb |
| Windows | AssinaLegis-*.msi |
Executar o instalador e seguir os passos (pode ser necessário reiniciar o computador) |
| jar | AssinaLegis-*.jar |
Possuíndo Java 21 instalado, o jar executará |
Após a instalação, o AssinaLegis aparece no menu de aplicativos do sistema operacional.
Na primeira execução, acesse Arquivo → Configurações e preencha:
| Campo | Descrição | Exemplo |
|---|---|---|
| URL da API | Endereço do backend Django | https://sapl.jatai.go.leg.br |
| Token de Acesso | Token DRF gerado na conta do usuário | 9944b09199c62bcf... |
| Certificado Digital | Caminho para o arquivo .pfx ou .p12 (certificado A1) |
/home/usuario/cert.pfx |
| Senha do Certificado | Senha do arquivo PFX/P12 | — |
Tokens A3 (smartcard/e-Token): não precisam de configuração de arquivo; basta conectar o dispositivo antes de assinar. O AssinaLegis detecta a biblioteca PKCS#11 automaticamente.
As configurações são salvas localmente de forma persistente (não é necessário reinserir a cada abertura).
O AssinaLegis possui dois modos de trabalho no visualizador de documentos:
- M1 (SAPL/API): busca documentos diretamente da API, exibe a lista de proposições e permite enviar os PDFs assinados de volta ao backend.
- M2 (Local): trabalha com arquivos PDF locais, sem depender da API para carregar documentos, ideal para assinatura por etapas e processamento manual.
- Exibe botão de atualização da lista de documentos vindos da API.
- Carrega itens do tipo proposição/documento remoto.
- Permite assinar e submeter arquivos assinados para a API.
- Quando não há token configurado, o M1 fica indisponível.
- Permite carregar PDFs locais em múltiplas etapas, sem limpar automaticamente a lista atual.
- Exibe botão para remover arquivos individualmente na lista.
- Exibe botão Limpar Lista para remover todos os arquivos locais carregados.
- Permite assinar documentos e salvar os arquivos assinados em pasta local.
- Use M1 quando o fluxo envolve documentos oficiais já disponíveis no SAPL e devolução automática para API.
- Use M2 quando os arquivos já estão no computador e o objetivo é assinatura local com controle manual do lote.
- Acesse Configurações e preencha URL da API e Token de Acesso.
- Ative o modo M1 (SAPL/API).
- Clique em Atualizar Lista para carregar os documentos pendentes.
- Selecione os documentos, posicione a marcação de assinatura no preview e clique em Assinar Todos Selecionados.
- Clique em Submeter Arquivos Assinados para enviar os PDFs assinados para a API.
- Ative o modo M2 (Local).
- Clique em Carregar PDFs quantas vezes forem necessárias para montar o lote por etapas.
- Use Remover em cada item para excluir arquivos específicos, quando necessário.
- Use Limpar Lista para reiniciar o lote local.
- Selecione os arquivos, posicione a marcação e clique em Assinar Todos Selecionados.
- Clique em Salvar Arquivos Assinados para escolher a pasta de destino.
- Busca automática de documentos pendentes de assinatura via API REST
- Visualização de PDF integrada com zoom e navegação por páginas
- Posicionamento visual da assinatura — clique na página para definir onde a assinatura visível será inserida
- Assinatura digital padrão ICP-Brasil (PAdES — PKCS#7 Detached)
- Certificados A1 — arquivos
.pfx/.p12 - Certificados A3 — tokens USB e smartcards (SafeSign, eToken, Gemalto, Epad, WatchData)
- Envio automático do PDF assinado de volta à API
- Assinatura em lote — múltiplos documentos de uma só vez
- Aparência personalizável da assinatura visível (cores de fundo, nome e data)
| Ferramenta | Versão mínima | Verificar |
|---|---|---|
| JDK | 21 | java -version |
| Maven | 3.9 | mvn -version |
| Git | qualquer | git --version |
O JDK deve incluir o módulo
jdk.crypto.cryptoki(presente por padrão nos JDKs 21 da Adoptium, Oracle e OpenJDK). Ele é necessário para o suporte a tokens A3 via SunPKCS11.
Para gerar instaladores nativos (perfis deb e windows) são necessários, respectivamente:
- Linux:
dpkg-debefakeroot—sudo apt install dpkg fakeroot - Windows: WiX Toolset v3+
git clone https://github.com/camara-jatai/assinalegis.git
cd assinalegismvn javafx:runmvn clean packageArtefatos gerados:
| Arquivo | Descrição |
|---|---|
dist/assinalegis-<versão>.jar |
JAR executável (uber-JAR com todas as dependências) |
target/assinalegis-<versão>.jar |
JAR leve com manifest apontando para target/libs/ |
target/libs/*.jar |
Dependências separadas |
Executar o JAR gerado:
java -jar dist/assinalegis-1.2.0-SNAPSHOT.jarmvn testmvn javafx:run -Dapp.debug=trueO projeto usa o plugin jpackage-maven-plugin para empacotar o aplicativo com uma runtime Java embutida. Os instaladores são salvos em dist/.
mvn clean package -PdebGera: dist/AssinaLegis-<versão>.deb
O pacote .deb instala o aplicativo em /opt/assinalegis/, cria entrada no menu de aplicativos e link simbólico em /usr/bin/assinalegis.
Execute em um ambiente Windows com o WiX Toolset instalado:
mvn clean package -PwindowsGera: dist/AssinaLegis-<versão>.msi
Por que os instaladores não estão no repositório? O
jpackageembute uma runtime Java completa no instalador, resultando em arquivos de 120–200 MB. Para evitar poluir o histórico Git, os instaladores prontos são distribuídos via Google Drive (veja a seção 1).
assinalegis/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── module-info.java ← declaração do módulo JPMS
│ │ │ └── br/leg/go/jatai/assinalegis/
│ │ │ ├── Launcher.java ← ponto de entrada
│ │ │ ├── App.java ← bootstrap JavaFX
│ │ │ ├── MainController.java ← controller da janela principal
│ │ │ ├── DocumentViewerController.java ← viewer PDF + seleção de assinatura
│ │ │ ├── ConfigController.java ← diálogo de configurações
│ │ │ ├── ConfigService.java ← persistência de configurações
│ │ │ ├── ApiService.java ← cliente HTTP REST
│ │ │ ├── TokenService.java ← detecção de tokens PKCS#11
│ │ │ └── AssinaturaService.java ← lógica de assinatura digital
│ │ └── resources/
│ │ ├── application.properties ← versão e flags (gerado pelo Maven)
│ │ ├── icon.png ← ícone da aplicação
│ │ └── br/leg/go/jatai/assinalegis/
│ │ ├── main.fxml ← layout da janela principal
│ │ ├── document_viewer.fxml ← layout do viewer de PDF
│ │ ├── config.fxml ← layout das configurações
│ │ └── styles.css ← estilos globais da UI
│ └── test/
│ └── java/... ← testes JUnit 5
├── packaging/
│ └── linux/
│ ├── postinst ← script pós-instalação .deb
│ └── prerm ← script pré-remoção .deb
├── dist/ ← artefatos gerados (gitignored)
├── pom.xml
└── .github/
├── copilot-instructions.md ← instruções para o GitHub Copilot
└── instructions/ ← instruções detalhadas por área
| Camada | Tecnologia | Versão |
|---|---|---|
| Linguagem | Java | 21 |
| UI | JavaFX + FXML | 21.0.2 |
| Apache PDFBox | 3.0.1 | |
| Criptografia | Bouncy Castle | 1.77 |
| HTTP Client | OkHttp | 4.9.3 |
| JSON | Jackson Databind | 2.16.1 |
| Testes | JUnit Jupiter | 5.10.2 |
| Build | Maven | 3.9+ |
┌──────────────────────────────────────────────┐
│ Camada de UI (JavaFX) │
│ MainController DocumentViewerController │
│ ConfigController │
├──────────────────────────────────────────────┤
│ Camada de Serviços │
│ ConfigService ApiService │
│ TokenService AssinaturaService │
├──────────────────────────────────────────────┤
│ Bibliotecas / Infraestrutura │
│ PDFBox BouncyCastle OkHttp PKCS#11 │
└──────────────────────────────────────────────┘
| Classe | Padrão | Responsabilidade |
|---|---|---|
ConfigService |
Singleton + Observer | Persiste configurações via java.util.prefs.Preferences; notifica observers ao alterar |
ApiService |
Singleton | Cliente OkHttp para o backend Django REST; detecta multipart automaticamente |
TokenService |
Singleton | Detecta bibliotecas PKCS#11 do sistema; abre KeyStore de tokens A3 |
AssinaturaService |
Stateless | Assina PDFs com PDFBox + BouncyCastle; gera assinatura visível |
Usuário seleciona documentos
│
▼
Clica na posição no PDF viewer ──► savedRect + savedPageIndex gravados no DocumentItem
│
▼
Clica "Assinar" ──► escolhe certificado (A1 ou A3) ──► informa PIN/senha
│
▼
AssinaturaService.assinarDocumentos()
├── Carrega originalBytes do PDF
├── Cria PDSignature (PAdES / PKCS#7 Detached)
├── Gera imagem da assinatura visível (bitmap com nome + data)
├── Assina com CMSSignedDataGenerator (BouncyCastle)
└── Envia PDF assinado para a API via ApiService
Esta seção destina-se a desenvolvedores que precisam contribuir com ou manter o código.
O projeto usa module-info.java. Ao adicionar dependências, obtenha o nome do módulo com:
jar --describe-module target/libs/<nova-dependencia>.jarEm seguida, adicione requires <nome.do.modulo>; ao module-info.java.
O TokenService testa automaticamente os caminhos de bibliotecas PKCS#11 conhecidos (SafeSign, eToken, Gemalto, Epad, WatchData) em Linux e Windows. Ao detectar erro de PIN (CKR_PIN_INCORRECT / CKR_PIN_LOCKED), a execução é interrompida imediatamente para evitar o bloqueio permanente do token.
Nunca inclua PINs ou senhas em logs, mesmo em modo debug.
O viewer renderiza PDFs a 200 DPI; o PDFBox opera a 72 DPI. A conversão é obrigatória:
double scaleFactor = 72.0 / 200.0;
float pdfX = (float)(viewerX * scaleFactor);
// Inversão do eixo Y (JavaFX: top-left → PDF: bottom-left)
float pdfY = mediaBox.getHeight() - (float)(viewerY * scaleFactor) - pdfHeight;{baseUrl}/api/{appLabel}/{modelName}/{id}/{action}/
Exemplos:
GET /api/base/casalegislativa/ → busca dados da casa legislativa
GET /api/docs/documento/42/pdf/ → baixa PDF do documento 42
POST /api/docs/documento/42/assinar/ → envia PDF assinado
Controlado pela propriedade app.debug.mode no pom.xml (padrão: false). Para habilitar sem alterar o POM:
mvn javafx:run -Dapp.debug=trueO diretório .github/instructions/ contém arquivos de instrução detalhados que o GitHub Copilot carrega automaticamente ao trabalhar em cada área do código:
| Arquivo | Quando é carregado |
|---|---|
arquitetura.instructions.md |
Qualquer arquivo .java |
javafx-ui.instructions.md |
Controllers, FXMLs, CSS |
assinatura-digital.instructions.md |
AssinaturaService, TokenService |
api-integracao.instructions.md |
ApiService, ConfigService |
build-packaging.instructions.md |
pom.xml, packaging/, module-info.java |
Este projeto está licenciado sob a GNU General Public License v3.0 (GPL-3.0).
Consulte o arquivo LICENSE para mais detalhes.
Desenvolvido pela Câmara Municipal de Jataí — GO