gblinux 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gblinux-0.1.0/PKG-INFO +123 -0
- gblinux-0.1.0/README.md +107 -0
- gblinux-0.1.0/pyproject.toml +34 -0
- gblinux-0.1.0/pyproject.toml.orig +35 -0
- gblinux-0.1.0/src/gblinux/__init__.py +8 -0
- gblinux-0.1.0/src/gblinux/__main__.py +5 -0
- gblinux-0.1.0/src/gblinux/aplicacao.py +86 -0
- gblinux-0.1.0/src/gblinux/atalho.py +39 -0
- gblinux-0.1.0/src/gblinux/config.py +35 -0
- gblinux-0.1.0/src/gblinux/download.py +35 -0
- gblinux-0.1.0/src/gblinux/erros.py +36 -0
- gblinux-0.1.0/src/gblinux/interface.py +264 -0
- gblinux-0.1.0/src/gblinux/parametros.py +69 -0
- gblinux-0.1.0/src/gblinux/pdf.py +435 -0
- gblinux-0.1.0/src/gblinux/servico.py +206 -0
- gblinux-0.1.0/src/gblinux/servidor.py +369 -0
- gblinux-0.1.0/src/gblinux/testar.py +150 -0
- gblinux-0.1.0/src/gblinux/token_pkcs11.py +255 -0
- gblinux-0.1.0/src/gblinux/worker.py +211 -0
gblinux-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: gblinux
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Assinador de PDF com token A3 ICP-Brasil para Linux
|
|
5
|
+
Author: renatormc
|
|
6
|
+
Author-email: renatormc <renatomartinsrmc@gmail.com>
|
|
7
|
+
Requires-Dist: asn1crypto>=1.5.1
|
|
8
|
+
Requires-Dist: cryptography>=50.0.2
|
|
9
|
+
Requires-Dist: pillow>=13.0.0
|
|
10
|
+
Requires-Dist: pyhanko>=0.38.1
|
|
11
|
+
Requires-Dist: pykcs11>=1.5.21
|
|
12
|
+
Requires-Dist: pyside6-essentials>=6.12.0
|
|
13
|
+
Requires-Dist: requests>=2.34.2
|
|
14
|
+
Requires-Python: >=3.13
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Assinador de PDF com token A3
|
|
18
|
+
|
|
19
|
+
Programa para Linux que assina documentos PDF com certificado digital ICP-Brasil A3 em token USB.
|
|
20
|
+
Ele fica aberto no computador e atende os pedidos de assinatura feitos pela página web do sistema.
|
|
21
|
+
|
|
22
|
+
Estas instruções são para Ubuntu e derivados, como Linux Mint e Pop!_OS. É preciso ter o
|
|
23
|
+
Python 3.13 ou mais novo; para conferir a versão, use `python3 --version`.
|
|
24
|
+
|
|
25
|
+
## 1. Instale o driver do token
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
sudo apt update
|
|
29
|
+
sudo apt install -y pcscd libpcsclite1 libccid pcsc-tools opensc libnss3-tools
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
cd ~/Downloads
|
|
33
|
+
|
|
34
|
+
# 1. Baixar e extrair o pacote oficial do ePass 2003
|
|
35
|
+
curl -O "https://site.solutinet.com.br/drivers/Token/ePass2003/Linux/ePass2003_Castle_20141128.tar.gz"
|
|
36
|
+
tar -zxvf ePass2003_Castle_20141128.tar.gz
|
|
37
|
+
|
|
38
|
+
# 2. Copiar para /usr/lib no formato esperado pelo Assinador Serpro
|
|
39
|
+
sudo rm -rf /usr/lib/ePass2003-Linux-x64
|
|
40
|
+
sudo cp -rf ~/Downloads/ePass2003-Castle-20141128 /usr/lib/ePass2003-Linux-x64
|
|
41
|
+
|
|
42
|
+
# 3. Criar atalhos da biblioteca PKCS#11 (libcastle.so.1.0.0) em /usr/lib
|
|
43
|
+
sudo cp /usr/lib/ePass2003-Linux-x64/x86_64/redist/libcastle.so.1.0.0 /usr/lib/
|
|
44
|
+
sudo ln -sf /usr/lib/libcastle.so.1.0.0 /usr/lib/libcastle.so
|
|
45
|
+
|
|
46
|
+
# 4. Configurar regras USB (udev) e reiniciar o daemon pcscd
|
|
47
|
+
sudo sh /usr/lib/ePass2003-Linux-x64/x86_64/config/config.sh
|
|
48
|
+
sudo systemctl enable --now pcscd.socket
|
|
49
|
+
sudo systemctl restart pcscd
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Depois disso, retire o token e conecte de novo.
|
|
53
|
+
|
|
54
|
+
## 2. Instale o pipx
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
sudo apt update
|
|
58
|
+
sudo apt install pipx libxcb-cursor0
|
|
59
|
+
pipx ensurepath
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Depois disso, **feche o terminal e abra outro**.
|
|
63
|
+
|
|
64
|
+
## 3. Instale o assinador
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pipx install gblinux
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 4. Crie a entrada no menu de aplicativos
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
gblinux --instalar-atalho
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
O **Assinador de PDF** vai aparecer no menu de aplicativos. Se não aparecer logo, encerre a sessão e
|
|
77
|
+
entre de novo.
|
|
78
|
+
|
|
79
|
+
## Como usar
|
|
80
|
+
|
|
81
|
+
1. Conecte o token.
|
|
82
|
+
2. Abra o **Assinador de PDF** pelo menu.
|
|
83
|
+
3. Deixe a janela aberta e peça a assinatura na página web.
|
|
84
|
+
4. Se a janela de senha aparecer, digite a senha do token.
|
|
85
|
+
|
|
86
|
+
Para encerrar, clique em **Fechar**.
|
|
87
|
+
|
|
88
|
+
**Cuidado com a senha:** cada senha errada gasta uma tentativa, e o token é bloqueado quando as
|
|
89
|
+
tentativas acabam.
|
|
90
|
+
|
|
91
|
+
## Como testar
|
|
92
|
+
|
|
93
|
+
Com o assinador aberto, rode em um terminal:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
testar-assinatura arquivo.pdf
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
O arquivo assinado é salvo na mesma pasta, como `arquivo-assinado.pdf`.
|
|
100
|
+
|
|
101
|
+
## Problemas comuns
|
|
102
|
+
|
|
103
|
+
| O que acontece | O que fazer |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `comando não encontrado` depois de instalar | Feche o terminal e abra outro |
|
|
106
|
+
| `Token não encontrado` | Confira se o token está conectado e se o driver foi instalado |
|
|
107
|
+
| A janela de senha não aparece, ou `Assinador não respondeu` | Feche o assinador, retire o token, conecte de novo e abra o assinador |
|
|
108
|
+
| `O assinador já está aberto` | Procure a janela que já está aberta |
|
|
109
|
+
| Aviso de porta ocupada | Feche o assinador antigo ou o outro programa que usa as portas 3399 e 4499 |
|
|
110
|
+
|
|
111
|
+
## Atualizar
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pipx upgrade gblinux
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Desinstalar
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
rm ~/.local/share/applications/gblinux.desktop
|
|
121
|
+
rm ~/.local/share/icons/hicolor/scalable/apps/gblinux.svg
|
|
122
|
+
pipx uninstall gblinux
|
|
123
|
+
```
|
gblinux-0.1.0/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Assinador de PDF com token A3
|
|
2
|
+
|
|
3
|
+
Programa para Linux que assina documentos PDF com certificado digital ICP-Brasil A3 em token USB.
|
|
4
|
+
Ele fica aberto no computador e atende os pedidos de assinatura feitos pela página web do sistema.
|
|
5
|
+
|
|
6
|
+
Estas instruções são para Ubuntu e derivados, como Linux Mint e Pop!_OS. É preciso ter o
|
|
7
|
+
Python 3.13 ou mais novo; para conferir a versão, use `python3 --version`.
|
|
8
|
+
|
|
9
|
+
## 1. Instale o driver do token
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
sudo apt update
|
|
13
|
+
sudo apt install -y pcscd libpcsclite1 libccid pcsc-tools opensc libnss3-tools
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
cd ~/Downloads
|
|
17
|
+
|
|
18
|
+
# 1. Baixar e extrair o pacote oficial do ePass 2003
|
|
19
|
+
curl -O "https://site.solutinet.com.br/drivers/Token/ePass2003/Linux/ePass2003_Castle_20141128.tar.gz"
|
|
20
|
+
tar -zxvf ePass2003_Castle_20141128.tar.gz
|
|
21
|
+
|
|
22
|
+
# 2. Copiar para /usr/lib no formato esperado pelo Assinador Serpro
|
|
23
|
+
sudo rm -rf /usr/lib/ePass2003-Linux-x64
|
|
24
|
+
sudo cp -rf ~/Downloads/ePass2003-Castle-20141128 /usr/lib/ePass2003-Linux-x64
|
|
25
|
+
|
|
26
|
+
# 3. Criar atalhos da biblioteca PKCS#11 (libcastle.so.1.0.0) em /usr/lib
|
|
27
|
+
sudo cp /usr/lib/ePass2003-Linux-x64/x86_64/redist/libcastle.so.1.0.0 /usr/lib/
|
|
28
|
+
sudo ln -sf /usr/lib/libcastle.so.1.0.0 /usr/lib/libcastle.so
|
|
29
|
+
|
|
30
|
+
# 4. Configurar regras USB (udev) e reiniciar o daemon pcscd
|
|
31
|
+
sudo sh /usr/lib/ePass2003-Linux-x64/x86_64/config/config.sh
|
|
32
|
+
sudo systemctl enable --now pcscd.socket
|
|
33
|
+
sudo systemctl restart pcscd
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Depois disso, retire o token e conecte de novo.
|
|
37
|
+
|
|
38
|
+
## 2. Instale o pipx
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
sudo apt update
|
|
42
|
+
sudo apt install pipx libxcb-cursor0
|
|
43
|
+
pipx ensurepath
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Depois disso, **feche o terminal e abra outro**.
|
|
47
|
+
|
|
48
|
+
## 3. Instale o assinador
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pipx install gblinux
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 4. Crie a entrada no menu de aplicativos
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
gblinux --instalar-atalho
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
O **Assinador de PDF** vai aparecer no menu de aplicativos. Se não aparecer logo, encerre a sessão e
|
|
61
|
+
entre de novo.
|
|
62
|
+
|
|
63
|
+
## Como usar
|
|
64
|
+
|
|
65
|
+
1. Conecte o token.
|
|
66
|
+
2. Abra o **Assinador de PDF** pelo menu.
|
|
67
|
+
3. Deixe a janela aberta e peça a assinatura na página web.
|
|
68
|
+
4. Se a janela de senha aparecer, digite a senha do token.
|
|
69
|
+
|
|
70
|
+
Para encerrar, clique em **Fechar**.
|
|
71
|
+
|
|
72
|
+
**Cuidado com a senha:** cada senha errada gasta uma tentativa, e o token é bloqueado quando as
|
|
73
|
+
tentativas acabam.
|
|
74
|
+
|
|
75
|
+
## Como testar
|
|
76
|
+
|
|
77
|
+
Com o assinador aberto, rode em um terminal:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
testar-assinatura arquivo.pdf
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
O arquivo assinado é salvo na mesma pasta, como `arquivo-assinado.pdf`.
|
|
84
|
+
|
|
85
|
+
## Problemas comuns
|
|
86
|
+
|
|
87
|
+
| O que acontece | O que fazer |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `comando não encontrado` depois de instalar | Feche o terminal e abra outro |
|
|
90
|
+
| `Token não encontrado` | Confira se o token está conectado e se o driver foi instalado |
|
|
91
|
+
| A janela de senha não aparece, ou `Assinador não respondeu` | Feche o assinador, retire o token, conecte de novo e abra o assinador |
|
|
92
|
+
| `O assinador já está aberto` | Procure a janela que já está aberta |
|
|
93
|
+
| Aviso de porta ocupada | Feche o assinador antigo ou o outro programa que usa as portas 3399 e 4499 |
|
|
94
|
+
|
|
95
|
+
## Atualizar
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pipx upgrade gblinux
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Desinstalar
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
rm ~/.local/share/applications/gblinux.desktop
|
|
105
|
+
rm ~/.local/share/icons/hicolor/scalable/apps/gblinux.svg
|
|
106
|
+
pipx uninstall gblinux
|
|
107
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gblinux"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Assinador de PDF com token A3 ICP-Brasil para Linux"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"asn1crypto>=1.5.1",
|
|
9
|
+
"cryptography>=50.0.2",
|
|
10
|
+
"pillow>=13.0.0",
|
|
11
|
+
"pyhanko>=0.38.1",
|
|
12
|
+
"pykcs11>=1.5.21",
|
|
13
|
+
"pyside6-essentials>=6.12.0",
|
|
14
|
+
"requests>=2.34.2",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[[project.authors]]
|
|
18
|
+
name = "renatormc"
|
|
19
|
+
email = "renatomartinsrmc@gmail.com"
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
gblinux = "gblinux:main"
|
|
23
|
+
testar-assinatura = "gblinux.testar:main"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["uv_build>=0.13.7,<0.14.0"]
|
|
27
|
+
build-backend = "uv_build"
|
|
28
|
+
|
|
29
|
+
[dependency-groups]
|
|
30
|
+
dev = ["pytest>=9.2.0"]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
testpaths = ["tests"]
|
|
34
|
+
pythonpath = ["tests"]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gblinux"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Assinador de PDF com token A3 ICP-Brasil para Linux"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "renatormc", email = "renatomartinsrmc@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"asn1crypto>=1.5.1",
|
|
12
|
+
"cryptography>=50.0.2",
|
|
13
|
+
"pillow>=13.0.0",
|
|
14
|
+
"pyhanko>=0.38.1",
|
|
15
|
+
"pykcs11>=1.5.21",
|
|
16
|
+
"pyside6-essentials>=6.12.0",
|
|
17
|
+
"requests>=2.34.2",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
gblinux = "gblinux:main"
|
|
22
|
+
testar-assinatura = "gblinux.testar:main"
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["uv_build>=0.13.7,<0.14.0"]
|
|
26
|
+
build-backend = "uv_build"
|
|
27
|
+
|
|
28
|
+
[dependency-groups]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=9.2.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
testpaths = ["tests"]
|
|
35
|
+
pythonpath = ["tests"]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Ponto de entrada do programa com janela (seção 9)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import signal
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from PySide6.QtCore import QDir, QLockFile, QStandardPaths, QTimer
|
|
11
|
+
from PySide6.QtWidgets import QApplication, QMessageBox
|
|
12
|
+
|
|
13
|
+
from . import config as configuracao
|
|
14
|
+
from .atalho import instalar_atalho
|
|
15
|
+
from .interface import JanelaPrincipal, PerguntasQt
|
|
16
|
+
from .servico import ServicoAssinatura
|
|
17
|
+
from .servidor import Contexto, PortaOcupada, Servidores
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _avisar(mensagem: str) -> None:
|
|
21
|
+
print(mensagem, file=sys.stderr)
|
|
22
|
+
QMessageBox.critical(None, "Assinador de PDF", mensagem)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main() -> int:
|
|
26
|
+
argumentos = argparse.ArgumentParser(description="Assinador de PDF com token A3")
|
|
27
|
+
argumentos.add_argument(
|
|
28
|
+
"--instalar-atalho", action="store_true", help="cria o atalho no menu de aplicativos e sai"
|
|
29
|
+
)
|
|
30
|
+
if argumentos.parse_args().instalar_atalho:
|
|
31
|
+
print(f"Atalho criado em {instalar_atalho()}")
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
35
|
+
aplicacao = QApplication(sys.argv[:1])
|
|
36
|
+
aplicacao.setApplicationName("gblinux")
|
|
37
|
+
aplicacao.setQuitOnLastWindowClosed(False)
|
|
38
|
+
|
|
39
|
+
pasta = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.RuntimeLocation)
|
|
40
|
+
trava = QLockFile(QDir(pasta or QDir.tempPath()).filePath("gblinux.lock"))
|
|
41
|
+
trava.setStaleLockTime(0)
|
|
42
|
+
if not trava.tryLock(0):
|
|
43
|
+
_avisar("O assinador já está aberto.")
|
|
44
|
+
return 1
|
|
45
|
+
|
|
46
|
+
config = configuracao.carregar()
|
|
47
|
+
perguntas = PerguntasQt()
|
|
48
|
+
janela: JanelaPrincipal | None = None
|
|
49
|
+
|
|
50
|
+
def ao_mudar_ocupado(ocupado: bool) -> None:
|
|
51
|
+
if janela is not None:
|
|
52
|
+
janela.ocupado_mudou.emit(ocupado)
|
|
53
|
+
|
|
54
|
+
def ao_resultado(mensagem: str) -> None:
|
|
55
|
+
if janela is not None:
|
|
56
|
+
janela.resultado_novo.emit(mensagem)
|
|
57
|
+
|
|
58
|
+
servico = ServicoAssinatura(config, perguntas, ao_mudar_ocupado)
|
|
59
|
+
servidores = Servidores(Contexto(config, servico, ao_resultado))
|
|
60
|
+
try:
|
|
61
|
+
servidores.iniciar()
|
|
62
|
+
except PortaOcupada as erro:
|
|
63
|
+
_avisar(
|
|
64
|
+
f"A porta {erro.porta} está ocupada: o assinador já está aberto ou outro "
|
|
65
|
+
"programa usa a porta."
|
|
66
|
+
)
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
def encerrar() -> None:
|
|
70
|
+
servidores.parar()
|
|
71
|
+
servico.encerrar()
|
|
72
|
+
|
|
73
|
+
janela = JanelaPrincipal(encerrar)
|
|
74
|
+
janela.show()
|
|
75
|
+
|
|
76
|
+
# O Python só trata sinais quando recupera o controle do laço de eventos do Qt.
|
|
77
|
+
def tratar_sinal(_sinal, _quadro) -> None:
|
|
78
|
+
QTimer.singleShot(0, janela.encerrar_sem_perguntar)
|
|
79
|
+
|
|
80
|
+
signal.signal(signal.SIGTERM, tratar_sinal)
|
|
81
|
+
signal.signal(signal.SIGINT, tratar_sinal)
|
|
82
|
+
relogio = QTimer()
|
|
83
|
+
relogio.timeout.connect(lambda: None)
|
|
84
|
+
relogio.start(200)
|
|
85
|
+
|
|
86
|
+
return aplicacao.exec()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Atalho no menu de aplicativos, instalado na pasta do usuário, sem root (seção 9)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shlex
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
_ICONE = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
11
|
+
<path fill="#f5f5f5" stroke="#444" stroke-width="3" d="M14 5h26l12 12v42H14z"/>
|
|
12
|
+
<path fill="none" stroke="#444" stroke-width="3" d="M40 5v12h12"/>
|
|
13
|
+
<path fill="none" stroke="#1565c0" stroke-width="4" stroke-linecap="round"
|
|
14
|
+
d="M20 46c5-14 8-14 9-6s4 7 7 1 4-5 8 0"/>
|
|
15
|
+
</svg>
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def instalar_atalho() -> Path:
|
|
20
|
+
dados = Path(os.environ.get("XDG_DATA_HOME") or Path.home() / ".local/share")
|
|
21
|
+
icone = dados / "icons/hicolor/scalable/apps/gblinux.svg"
|
|
22
|
+
icone.parent.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
icone.write_text(_ICONE, encoding="utf-8")
|
|
24
|
+
|
|
25
|
+
atalho = dados / "applications/gblinux.desktop"
|
|
26
|
+
atalho.parent.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
comando = shlex.join([sys.executable, "-m", "gblinux"])
|
|
28
|
+
atalho.write_text(
|
|
29
|
+
"[Desktop Entry]\n"
|
|
30
|
+
"Type=Application\n"
|
|
31
|
+
"Name=Assinador de PDF\n"
|
|
32
|
+
"Comment=Assina PDFs com o token A3\n"
|
|
33
|
+
f"Exec={comando}\n"
|
|
34
|
+
"Icon=gblinux\n"
|
|
35
|
+
"Terminal=false\n"
|
|
36
|
+
"Categories=Office;\n",
|
|
37
|
+
encoding="utf-8",
|
|
38
|
+
)
|
|
39
|
+
return atalho
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Configuração lida do ambiente. Os valores padrão vêm da especificação."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
PORTA_PAGINA = 3399
|
|
9
|
+
PORTA_DIRETA = 4499
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _int(nome: str, padrao: int) -> int:
|
|
13
|
+
valor = os.environ.get(nome, "").strip()
|
|
14
|
+
return int(valor) if valor else padrao
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class Config:
|
|
19
|
+
biblioteca_pkcs11: str = "/usr/lib/libcastle.so"
|
|
20
|
+
arquivo_cas: str | None = None
|
|
21
|
+
limite_bytes: int = 100 * 1024 * 1024
|
|
22
|
+
tempo_limite_s: float = 600.0
|
|
23
|
+
tempo_conectar_s: float = 30.0
|
|
24
|
+
tempo_ler_s: float = 120.0
|
|
25
|
+
# Tempo que o driver recebe para terminar depois de um SIGTERM.
|
|
26
|
+
tempo_encerrar_s: float = 15.0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def carregar() -> Config:
|
|
30
|
+
return Config(
|
|
31
|
+
biblioteca_pkcs11=os.environ.get("ASSINADOR_PKCS11_LIB") or Config.biblioteca_pkcs11,
|
|
32
|
+
arquivo_cas=os.environ.get("ASSINADOR_CA_BUNDLE") or None,
|
|
33
|
+
limite_bytes=_int("ASSINADOR_LIMITE_BYTES", Config.limite_bytes),
|
|
34
|
+
tempo_limite_s=_int("ASSINADOR_TEMPO_LIMITE_S", int(Config.tempo_limite_s)),
|
|
35
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Download dos PDFs (seção 4.5)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from urllib.parse import urlsplit
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from .config import Config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FalhaDownload(Exception):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def baixar(url: str, config: Config) -> bytes:
|
|
17
|
+
if urlsplit(url).scheme.lower() not in ("http", "https"):
|
|
18
|
+
raise FalhaDownload("somente URLs http e https são aceitas")
|
|
19
|
+
dados = bytearray()
|
|
20
|
+
try:
|
|
21
|
+
# Em redirecionamentos, o requests só aceita http e https, e recusa file:// ou ftp://.
|
|
22
|
+
with requests.get(
|
|
23
|
+
url,
|
|
24
|
+
stream=True,
|
|
25
|
+
timeout=(config.tempo_conectar_s, config.tempo_ler_s),
|
|
26
|
+
verify=config.arquivo_cas or True,
|
|
27
|
+
) as resposta:
|
|
28
|
+
resposta.raise_for_status()
|
|
29
|
+
for bloco in resposta.iter_content(1024 * 1024):
|
|
30
|
+
dados += bloco
|
|
31
|
+
if len(dados) > config.limite_bytes:
|
|
32
|
+
raise FalhaDownload("o arquivo excede o tamanho máximo permitido")
|
|
33
|
+
except requests.RequestException as erro:
|
|
34
|
+
raise FalhaDownload(str(erro)) from erro
|
|
35
|
+
return bytes(dados)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Mensagens de erro de assinatura (seção 7), mantidas iguais às do original."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
CANCELADO = "ASSINATURA DE DOCUMENTO CANCELADO PELO USUÁRIO"
|
|
6
|
+
TOKEN_NAO_ENCONTRADO = "Token não encontrado"
|
|
7
|
+
SENHA_INCORRETA = "Senha incorreta para o token"
|
|
8
|
+
PDF_INVALIDO = (
|
|
9
|
+
"Documento não pode ser aberto e editado. Verifique se o documento esta protegido de "
|
|
10
|
+
"edição ou contem senha e se os dados do TOKEN foram informados corretamente!"
|
|
11
|
+
)
|
|
12
|
+
NENHUM_CERTIFICADO = "Nenhum certificado valido. Procure o gestor da sua instituição!"
|
|
13
|
+
FALHA_LISTAR = (
|
|
14
|
+
"Houve um erro ao listar os dados do Token, verifique se o token ta inserido "
|
|
15
|
+
"corretamente e o dados informados"
|
|
16
|
+
)
|
|
17
|
+
ERRO_DRIVER = "Erro desconhecido, Verifique se foi instalado corretamente os drivers do token"
|
|
18
|
+
SENHA_BLOQUEADA = "Token bloqueado por excesso de tentativas de senha."
|
|
19
|
+
SEM_RESPOSTA = "Assinador não respondeu. Reinsira o token e tente novamente."
|
|
20
|
+
GOVBR_INDISPONIVEL = "Assinatura via Gov.br não está disponível nesta versão"
|
|
21
|
+
SEM_DOCUMENTO = "Nenhum arquivo para fazer a assinatura!"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def chave_nao_encontrada(cpf: str) -> str:
|
|
25
|
+
return (
|
|
26
|
+
f"Chave privada do token não encontrada para o CPF [{cpf}]. Verifique se o token está "
|
|
27
|
+
"inserido, se a chave privada está correta e se o certicado está valido!"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ErroAssinatura(Exception):
|
|
32
|
+
"""Erro que vira a resposta JSON da seção 7."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, mensagem: str) -> None:
|
|
35
|
+
super().__init__(mensagem)
|
|
36
|
+
self.mensagem = mensagem
|