08 - Assinatura ICP-Brasil¶
Status: Implemented
Objetivo¶
Especificar a assinatura digital ICP-Brasil/PAdES para documentos que precisem produzir efeitos legais substitutivos conforme política institucional.
Princípio arquitetural fundamental¶
A assinatura é uma operação de nuvem, executada exclusivamente pela
EnfaseCloud.WebApi, após o upload e persistência do artefato original.
O EnfaseCloud.Scan (estação on-prem) nunca aplica assinatura. Suas responsabilidades são:
- Gerar o artefato PDF/A íntegro com XMP e OCR.
- Calcular o hash do artefato pré-assinatura.
- Registrar no manifesto que assinatura é necessária (
RequiredPending) ou não requerida (NotRequired). - Enviar artefato + manifesto + status para a WebApi via upload.
A WebApi recebe o artefato, persiste, e então aciona o provedor de assinatura ICP-Brasil/PAdES quando a política exige.
Escopo¶
- Formato: PAdES (PDF Advanced Electronic Signatures) conforme MP 2.200-2/2001 e ICP-Brasil.
- Responsável:
EnfaseCloud.WebApi— integração com provedor de assinatura centralizado. - Gatilho: após upload bem-sucedido, se
SignaturePolicy.Required = truepara o tenant/tipo documental.
Requisitos¶
WebApi (único executor da assinatura)¶
- A WebApi deve definir por tenant/tipo documental se assinatura é obrigatória (
SignaturePolicy). - Quando obrigatória, a WebApi deve acionar o provedor de assinatura após persistir o artefato original.
- A WebApi deve calcular e registrar o hash do artefato assinado (distinto do hash pré-assinatura enviado pelo Scan).
- A validação deve verificar cadeia, validade, revogação quando disponível, integridade e carimbo de tempo quando exigido.
- O manifesto deve ser atualizado com certificado, emissor, sujeito, serial, data, status e política aplicada.
EnfaseCloud.Scan (estação on-prem)¶
- O Scan deve registrar no manifesto o campo
SignatureStatuscomoNotRequiredouRequiredPending. - O Scan não aplica, não chama e não valida assinatura ICP-Brasil.
- O Scan não deve bloquear o upload por ausência de assinatura — esse controle cabe à WebApi.
Status e rastreabilidade¶
- Quando assinatura inválida, a WebApi deve atualizar status sem alterar o artefato original.
- O
EnfaseCloud.WebAppdeve exibir status de assinatura com rastreabilidade de quem, quando e com qual certificado.
Estados¶
| Estado | Quem define | Significado |
|---|---|---|
NotRequired |
Scan / WebApi | Política não exige assinatura para este tipo documental. |
RequiredPending |
Scan | Enviado pelo Scan; aguarda ação da WebApi. |
SignedValid |
WebApi | Assinatura aplicada e validada. |
SignedInvalid |
WebApi | Assinatura presente mas inválida. |
ValidationError |
WebApi | Erro durante validação — não conclusivo. |
ExpiredAfterSigning |
WebApi | Certificado expirou após a assinatura. |
Fluxo¶
EnfaseCloud.Scan EnfaseCloud.WebApi
───────────────── ──────────────────
Gera PDF/A Recebe upload
Calcula hash pré-assinatura ──────► Persiste artefato original
Registra RequiredPending Chama provedor ICP-Brasil/PAdES
Envia upload Calcula hash pós-assinatura
Registra SignedValid / SignedInvalid
Aceite¶
- Documento com assinatura válida exibe status, certificado e data.
- Documento com
RequiredPendingtem status visível no WebApp; o arquivo permanece acessível. - Documento com assinatura inválida não é tratado como conforme para valor probatório.
- O Scan não é bloqueado por ausência de assinatura — segue para upload normalmente.
Armazenamento e resolução de certificados¶
Princípio¶
O certificado ICP-Brasil/PAdES não é armazenado no banco de dados — o banco guarda apenas referências (identificadores) para o cofre de chaves. A chave privada nunca sai do vault.
Hierarquia de resolução (por cliente com fallback para tenant)¶
ScanController recebe upload com SignatureStatus=RequiredPending
│
└── SignatureService.ResolveSigningCertificateAsync(idCliente, tenantKey)
│
├── 1. Existe certificado registrado para o Cliente (idCliente)?
│ └── SIM → usa CertificadoCliente
│
├── 2. Existe certificado registrado para o Tenant (tenantKey)?
│ └── SIM → usa CertificadoTenant (fallback)
│
└── 3. Nenhum encontrado
└── SignatureStatus = ValidationError
Mensagem: "Certificado de assinatura não configurado para este cliente/tenant."
Quando usar certificado do cliente: quando o signatário legal deve ser o próprio cliente (ex.: órgão público digitalizando seus documentos).
Quando usar certificado do tenant: quando o prestador de serviço GED é o responsável pela digitalização e assina em nome próprio (ex.: empresa de digitalização prestando serviço para vários clientes).
Modelo de armazenamento — Azure Key Vault¶
Cada certificado é um Certificate no Azure Key Vault, identificado por nome canônico:
| Entidade | Nome no Key Vault | Exemplo |
|---|---|---|
| Tenant | sig-tenant-{tenantKey} |
sig-tenant-prefeitura-sp |
| Cliente | sig-client-{idCliente} |
sig-client-42 |
O tenantKey e idCliente são resolvidos a partir do contexto de autenticação do upload.
Modelo de dados¶
Por que tabela separada e não campos no Cliente?¶
Três razões objetivas:
- O certificado do tenant não tem
IdCliente— o fallback tenant → cliente exige um registro semIdCliente. Campos noClientenão teriam onde guardar o cert do tenant. - Um cliente acumula múltiplos certificados ao longo do tempo — rotação (A1 expira em 1–3 anos), migração A1 → A3, revogação. A tabela separada preserva o histórico auditável; sobrescrever campos no
Clientedestruiria o rastro de qual cert assinou cada documento. - Separação de responsabilidades —
Clienteé entidade de negócio;CertificadoAssinaturaé infraestrutura de segurança com ciclo de vida próprio (validade, revogação, thumbprint). Misturar viola coesão.
O que faz sentido no Cliente é um FK de conveniência para o certificado ativo, evitando joins frequentes na rota crítica de assinatura:
ALTER TABLE Cliente ADD IdCertificadoAssinaturaAtivo INT NULL
REFERENCES CertificadoAssinatura(Id);
-- Atualizado automaticamente quando um novo certificado é ativado.
-- NULL = usa o cert do tenant como fallback.
Tabela CertificadoAssinatura¶
CREATE TABLE CertificadoAssinatura (
Id INT PRIMARY KEY IDENTITY,
TenantKey NVARCHAR(128) NOT NULL, -- tenant obrigatório
IdCliente INT NULL, -- NULL = certificado do tenant
KeyVaultCertName NVARCHAR(256) NOT NULL, -- nome no Key Vault
KeyVaultUri NVARCHAR(512) NOT NULL, -- URI do vault
Thumbprint NVARCHAR(64) NOT NULL, -- para auditoria e rastreabilidade
NomeCertificado NVARCHAR(256) NOT NULL, -- CN do certificado ICP-Brasil
Emissor NVARCHAR(256) NOT NULL, -- AC emissora ICP-Brasil
ValidoDe DATETIME2 NOT NULL,
ValidoAte DATETIME2 NOT NULL,
TipoPortador NVARCHAR(16) NOT NULL DEFAULT 'A1', -- A1, A3, CloudHSM
Ativo BIT NOT NULL DEFAULT 1,
CriadoEmUtc DATETIME2 NOT NULL DEFAULT GETUTCDATE()
-- SEM UNIQUE (TenantKey, IdCliente): um cliente pode ter múltiplos certs
-- (histórico de rotação). O ativo é apontado por Cliente.IdCertificadoAssinaturaAtivo.
);
Referência rápida no Cliente¶
-- Coluna adicionada à tabela Cliente existente:
ALTER TABLE Cliente
ADD IdCertificadoAssinaturaAtivo INT NULL
REFERENCES CertificadoAssinatura(Id);
-- Resolução: se NULL → busca cert do tenant em CertificadoAssinatura WHERE IdCliente IS NULL
Tipos de certificado suportados¶
| Tipo | Armazenamento | Uso recomendado | Validade ICP-Brasil |
|---|---|---|---|
| A1 | Azure Key Vault (PFX importado) | Dev/homol, clientes sem exigência de HSM | 1–3 anos |
| A3 | Azure Key Vault (HSM-backed) | Produção com exigência legal mais rigorosa | 3–5 anos |
| CloudHSM | Azure Managed HSM | Alta segurança / órgãos públicos | Configurável |
Fluxo de configuração (operação)¶
1. Gerar/adquirir certificado ICP-Brasil junto à AC credenciada
2. Importar PFX no Azure Key Vault (script de provisionamento)
3. Registrar na tabela CertificadoAssinatura:
- Se para o tenant inteiro: IdCliente = NULL
- Se para um cliente específico: IdCliente = <id>
4. O SignatureService resolve automaticamente na ordem: cliente → tenant
Atualização da spec de requisitos¶
- A WebApi deve implementar
ICertificateResolvercom a hierarquia cliente → tenant. - Quando nenhum certificado for encontrado,
SignatureStatus = ValidationErrore o documento permanece acessível. - O manifesto deve registrar
ThumbprinteKeyVaultCertNamepara auditoria. - Certificados expirados devem gerar alerta e
SignatureStatus = ExpiredAfterSigning(validação retroativa).
Implementação¶
Entregues (status: Implemented)¶
| Requisito | Implementação | Arquivo |
|---|---|---|
| Assinatura exclusivamente na WebApi pós-upload | Spec 00/01/04/05/08 revisadas | Specs |
RequireSignature por tipo documental |
TipoDocumental.RequireSignature (Library) + LocalTipoDocumental.RequireSignature (Scan SQLite) |
Library/Models/TipoDocumental.cs |
RequireSignature sincronizado via catálogos |
TipoDocumentalDto.RequireSignature + SyncCatalogsController expõe + SyncOrchestrator faz upsert |
Sync/Dtos/CatalogDtos.cs |
| Detecção offline de política de assinatura | IIndexationService.TipoDocumentalRequiresSignatureAsync |
Home/IndexationService.cs |
SignatureStatus no XMP do artefato |
PdfComplianceMetadata.SignatureStatus + mcgi:statusAssinatura no XMP |
Library/Services/PdfComplianceService.cs |
Scan registra NotRequired ou RequiredPending |
ScanCaptureService.ComposePdfAsync determina e grava no XMP |
Scan/Scan/ScanCaptureService.cs |
SignatureStatusCodes (6 estados) |
SignatureStatusCodes enum-like com todas as transições da spec |
Scan/Scan/ScanCaptureResult.cs |
| Operador vê status de assinatura pós-salvar | "assinatura pendente (WebApi)" na mensagem |
UI/ScanUserControl.cs |
Status NotRequired/RequiredPending no manifesto |
Compliance.IcpBrasilSignatureRequiredWhenApplicable + SignatureStatus |
Library/Models/DigitalizationCompliance.cs |
Pendente (bloqueador jurídico)¶
| Requisito | Situação |
|---|---|
| Integração ICP-Brasil em produção | Bloqueador jurídico — sem ICP-Brasil em prod, nenhum documento produz efeito legal substitutivo (Decreto 10.278/2020 art. 5º). |
| Provedor de assinatura PAdES | Aguarda decisão de provedor (A1/A3/nuvem). IcpBrasilDocumentSignatureProvider e DevDocumentSignatureProvider existem na WebApi mas sem certificado real. |
| Hash pós-assinatura | Dois hashes especificados na spec — pré-assinatura ✅ (Scan), pós-assinatura ❌ (WebApi calcula e registra após assinar). |
| Atualização do manifesto pós-assinatura | Certificado, emissor, serial, data, política — WebApi deve gravar após SignedValid. |
Migration SQL require_signature no banco da WebApi |
Coluna RequireSignature adicionada ao modelo mas migration SQL pendente no banco GED. |