Notary¶
Servicio de firma digital de documentos PDF con soporte PAdES y firma visual.
| Propiedad | Valor |
|---|---|
| Puerto | :8001 (desarrollo) |
| Framework | FastAPI 0.104.1 |
| Firma criptografica | pyHanko 0.27.1 (PAdES-B-T) |
| Firma visual | ReportLab + pypdf |
| Analisis PDF | PyMuPDF (fitz) |
| Runtime | Python 3.11, Gunicorn 21.2.0 |
| Deploy | Docker, Fly.io (internal-only, sin IP publica) |
Que hace¶
Notary recibe un PDF y datos del firmante, aplica una firma digital (PAdES criptografica o visual) y opcionalmente estampa informacion del documento (numero, ciudad, fecha).
flowchart LR
A[Backend] -->|POST /sign-pdf| B[Notary]
B --> C{Certificado via multipart?}
C -->|Si| D[Firma PAdES-B-T + Visual]
C -->|No + FALLBACK=true| E[Firma Visual]
C -->|No + FALLBACK=false| F[Error 400]
D --> G[PDF firmado]
E --> G
G -->|Response| A
Modos de firma¶
Firma PAdES (recomendado para produccion)¶
Firma digital criptografica embebida en el PDF, cumpliendo el estandar PAdES-B-T (Basic with Time):
- Firma criptografica con certificado PKCS#12 (.p12)
- Timestamp de servidor TSA (Time Stamping Authority)
- Representacion visual con datos del firmante
- Verificable en Adobe Acrobat Reader (clic en la firma)
Firma Visual (solo testing)¶
Firma con elementos visuales (texto y lineas) sin componente criptografico:
- Nombre, cargo, departamento, entidad del firmante
- Timestamp de servidor
- No es verificable criptograficamente
Comportamiento por ambiente¶
| Ambiente | ENVIRONMENT |
FALLBACK_TO_VISUAL |
Si no hay certificado |
|---|---|---|---|
| Desarrollo | test |
true (default) |
Usa firma visual |
| Produccion | prd |
false (default) |
Error 400 |
Estructura del proyecto¶
GDI-Notary/
├── app/
│ ├── main.py # Endpoints FastAPI
│ ├── config.py # Constantes y configuracion
│ ├── auth.py # Autenticacion API Key
│ ├── layout.py # Algoritmo posicionamiento 2 columnas
│ ├── signature_inserter.py # Firma visual (ReportLab + pypdf)
│ ├── document_stamper.py # Estampado primera/ultima pagina
│ ├── validators.py # Validaciones de entrada
│ ├── certificate_loader.py # Carga certificados .p12 por tenant
│ └── pades_signer.py # Firma PAdES con pyHanko
├── certs/
│ ├── {tenant_id}.p12 # Certificados por tenant
│ └── passwords.json # Mapeo tenant -> password
├── fonts/
│ └── Roboto-Bold.ttf # Fuente para firma visual
├── scripts/
│ └── generate_test_cert.py # Genera certificados de prueba
├── gunicorn_conf.py # Config produccion
├── Dockerfile
└── requirements.txt
Endpoints¶
| Endpoint | Metodo | Auth | Descripcion |
|---|---|---|---|
/sign-pdf |
POST | Si | Firma PDF (PAdES o visual) |
/sign-pdf/verify |
POST | Si | Verifica firmas PAdES de un PDF |
/stamp-number |
POST | Si | Estampa numero/fecha y devuelve coordenadas para FirmadorGDI |
/health |
GET | No | Health check (HTTP, sin auth) |
/certificate/{tenant_id} |
GET | Si | Info de certificado |
/certificates |
GET | Si | Lista certificados |
Variables de entorno¶
| Variable | Default | Descripcion |
|---|---|---|
ENVIRONMENT |
test |
Ambiente de ejecucion (test o prd) |
API_KEY |
(requerida) | API Key de autenticacion. Falla si no esta seteada |
CERTS_DIR |
./certs |
Directorio de certificados |
TSA_URL |
http://timestamp.digicert.com |
Servidor de timestamp |
TSA_TIMEOUT |
3 |
Timeout por intento TSA (segundos) |
TSA_RETRIES |
2 |
Reintentos ante fallo TSA |
FALLBACK_TO_VISUAL |
false |
Usar firma visual si no llega certificado |
GUNICORN_WORKERS |
(ver fly.toml) | Workers de Gunicorn |
GUNICORN_TIMEOUT |
90 |
Timeout en segundos |
FALLBACK_TO_VISUAL
El default de FALLBACK_TO_VISUAL en config.py es false. Para desarrollo
sin certificado, setear explicitamente FALLBACK_TO_VISUAL=true.
Flujo completo de firma¶
sequenceDiagram
participant B as Backend
participant N as Notary
participant TSA as Timestamp Authority
B->>N: POST /sign-pdf (PDF + datos firmante)
N->>N: Validar PDF (formato, tamano)
N->>N: Validar parametros firma
N->>N: Validar tenant_id (path traversal check)
alt Certificado llega via multipart (cert_file + cert_password)
N->>N: load_certificate_from_bytes (tempfile en /dev/shm, chmod 600)
N->>N: Calcular posicion (layout 2 columnas)
N->>N: Estampar documento async (si aplica)
N->>TSA: Solicitar timestamp (RetryingTimestamper + circuit breaker)
TSA-->>N: Timestamp token
N->>N: Firma PAdES-B-T + visual (sign_pdf_combined)
else Sin certificado + FALLBACK=true
N->>N: Calcular posicion (layout 2 columnas)
N->>N: Estampar documento async (si aplica)
N->>N: Firma visual (ReportLab + pypdf)
else Sin certificado + FALLBACK=false
N-->>B: Error 400 CERTIFICATE_NOT_PROVIDED
else TSA caido (circuit breaker abierto)
N-->>B: Error 503 TSA_UNAVAILABLE
end
N-->>B: PDF firmado (X-Signature-Type header)