Ir para o conteúdo

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:

  1. Gerar o artefato PDF/A íntegro com XMP e OCR.
  2. Calcular o hash do artefato pré-assinatura.
  3. Registrar no manifesto que assinatura é necessária (RequiredPending) ou não requerida (NotRequired).
  4. 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 = true para 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 SignatureStatus como NotRequired ou RequiredPending.
  • 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.WebApp deve 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 RequiredPending tem 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:

  1. O certificado do tenant não tem IdCliente — o fallback tenant → cliente exige um registro sem IdCliente. Campos no Cliente não teriam onde guardar o cert do tenant.
  2. 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 Cliente destruiria o rastro de qual cert assinou cada documento.
  3. Separação de responsabilidadesCliente é 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 ICertificateResolver com a hierarquia cliente → tenant.
  • Quando nenhum certificado for encontrado, SignatureStatus = ValidationError e o documento permanece acessível.
  • O manifesto deve registrar Thumbprint e KeyVaultCertName para 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.