💡 Exemplo completo em funcionamento disponível no GitHub:
digital-signing-certificate-validity-dotnet
O Problema de Conformidade que Ninguém Vê Até que um Auditor Apareça
Um serviço de assinatura funciona por três anos sem erro. Documentos são enviados, os destinatários os aceitam, nada nos logs indica um problema. Então o validador de uma contraparte sinaliza um lote como inválido, e a investigação revela duas causas: as assinaturas foram criadas com SHA‑1 e, nos últimos quatro meses, o certificado estava expirado.
Ambas as falhas foram silenciosas no momento da assinatura. É isso que o GroupDocs.Signature 26.9 muda.
A imposição de validade do certificado é o novo comportamento padrão para assinatura digital em .NET: um certificado fora da sua janela de validade é rejeitado em vez de ser usado. Ele chega com dois companheiros — SHA‑256 como algoritmo de resumo padrão para PDF, e um LogLevel que finalmente filtra — e, juntos, deslocam três classes de falha do destinatário para o remetente, onde ainda podem ser corrigidas.
Por que o Sucesso Silencioso é o Resultado Mais Caro
Assinar é incomum porque a parte que comete o erro não é a parte que o descobre. Uma fatura malformada falha no seu próprio sistema; uma assinatura inválida falha no sistema de outra pessoa, semanas depois, sem diagnóstico que você possa ler.
Essa assimetria explica por que “a API retornou sucesso” não é uma garantia útil aqui. As configurações antigas eram otimizadas para não interromper o chamador, e o custo recaiu sobre o destinatário e, eventualmente, sobre quem teve que re‑assinar e reenviar centenas de documentos.
Alteração 1: Certificados Expirados São Rejeitados
A mudança principal. Sign agora lança GroupDocsSignatureException quando a validade do certificado terminou ou ainda não começou, e nada é gravado no disco.
try
{
signature.Sign(outputPath, options);
return true;
}
catch (GroupDocsSignatureException ex)
{
Console.WriteLine($" Rejected: {ex.Message}");
return false;
}
A mensagem indica o certificado e a propriedade que o permitiria, de modo que um operador lendo uma linha de log possa agir sem abrir a documentação. Para um pipeline que atualiza para 26.9 e começa a falhar, essa é quase sempre a causa — e a resposta correta é renovação, não supressão.
Quando você realmente precisa do comportamento antigo, há uma propriedade:
var options = new DigitalSignOptions(certificate)
{
Password = certificatePassword,
AllowExpired = true
};
O documento é assinado e um aviso vai para o logger. Os validadores ainda rejeitam o resultado, porque AllowExpired controla o que a biblioteca permite, não o que o certificado vale. A bandeira companheira AllowNotYetValid cobre o outro extremo da janela e é deliberadamente independente: permitir um certificado expirado não permite silenciosamente um certificado com data futura.
Alteração 2: SHA‑256 por Padrão
Assinaturas digitais de PDF agora são gravadas com SHA‑256 no formato adbe.pkcs7.detached que os validadores atuais esperam. Versões anteriores usavam SHA‑1.
var options = new DigitalSignOptions(certificate)
{
Password = certificatePassword,
HashAlgorithm = HashAlgorithm.Sha256,
Reason = "Approved",
Location = "Head office"
};
Definir a propriedade explicitamente só é necessário para avançar — Sha384 ou Sha512 quando uma política os exige — ou para permanecer em Sha1 para um validador que não consegue lidar com outro algoritmo. Um carimbo de tempo adicionado à assinatura usa o mesmo resumo.
A verificação mudou na mesma versão e na mesma direção: DigitalVerifyOptions sem critérios antes era quase um no‑op, e agora executa uma verificação criptográfica completa, de modo que um documento alterado após a assinatura é relatado como inválido.
Alteração 3: LogLevel Realmente Filtra
SignatureSettings aceita um logger há muito tempo. Antes da 26.9 o nível era ignorado, então toda mensagem chegava independentemente e a maioria dos serviços desativava o logging ao invés de se afogar em rastros.
O exemplo torna a diferença mensurável ao assinar o mesmo documento três vezes com um logger contador:
var levels = new Dictionary<string, LogLevel>
{
["None"] = LogLevel.None,
["Warning | Error"] = LogLevel.Warning | LogLevel.Error,
["All"] = LogLevel.All
};
None produz zero mensagens, Warning | Error mantém o único aviso gerado pelo certificado expirado permitido, e All adiciona um rastreamento por etapa. O logger contador em si é o ponto de integração para sua própria pilha:
public void Warning(string message)
{
Warnings++;
WarningMessages.Add(message);
}
Implemente esses três métodos em Serilog, NLog ou Application Insights e o diagnóstico da biblioteca será registrado onde o restante do seu serviço registra.
O nível de log altera as exceções que recebo?
Não, e vale a pena ser explícito porque os dois parecem relacionados. LogLevel filtra o que chega ao ILogger. Exceções são lançadas para o seu código independentemente: um certificado expirado sem AllowExpired ainda lança em LogLevel.None, e seu bloco catch se comporta da mesma forma. Diagnósticos e fluxo de controle são canais separados, o que torna seguro executar a produção em Warning | Error.
A Rejeição é Mais Barata do que Parece
A objeção a uma parada abrupta é operacional: um lote noturno que antes terminava agora falha às 02:00 e alguém é acionado.
Esse é um custo real, e ainda assim é o menor. Um lote rejeitado gera um alerta, uma renovação e uma nova execução, tudo dentro dos seus próprios sistemas. Um lote assinado com certificado expirado é descoberto pelo destinatário, o que gera um tópico de suporte, a re‑emissão de cada documento afetado e uma conversa desconfortável sobre há quanto tempo isso acontece.
O exemplo torna a falha concreta em vez de teórica: ele assina intencionalmente com um certificado expirado, captura a exceção e imprime a mensagem, para que você veja exatamente o que seus logs conterão antes da atualização chegar à produção. Recomendo executar esse método contra seu próprio repositório de certificados antes de programar a atualização de versão.
O Que Fazer Antes de Atualizar
Três verificações, na ordem de probabilidade de causar problemas.
- Verifique a expiração dos certificados em todos os caminhos de assinatura, inclusive aqueles que rodam mensal ou trimestralmente — são esses os locais onde um certificado expirado permanece mais tempo oculto.
- Procure por
HashAlgorithm: se nada o definir, seus resumos mudarão de SHA‑1 para SHA‑256 na atualização, o que é uma melhoria que ainda deve constar nas notas de versão. - Decida deliberadamente um nível de log. O padrão honesto para um serviço é
Warning | Error;Allserve para reproduzir um problema específico, eNonesignifica abrir mão do único sinal que indica que uma assinatura foi feita sob uma exceção.
Verificação Mudou na Mesma Direção
É fácil perder isso, porque nada no código de chamada precisa mudar. DigitalVerifyOptions sem critérios antes era quase um no‑op: comparava os critérios fornecidos e, não havendo nenhum, tinha pouco a dizer. A partir da 26.9 a mesma chamada realiza uma verificação criptográfica completa de cada assinatura digital de PDF.
Para um serviço que verifica documentos recebidos, isso representa uma atualização silenciosa de “há uma assinatura aqui” para “esta assinatura corresponde a este conteúdo”. Vale saber antes de ver um documento começar a falhar na verificação que passou no mês passado: o documento provavelmente foi alterado, e a verificação anterior simplesmente não o detectava.
Os Certificados no Exemplo
Um detalhe que vale a pena copiar em vez do código: o exemplo não inclui chave privada. TestCertificates.cs cria três PFXs autoassinados em memória no tempo de execução — válido, expirado no ano passado, válido a partir do próximo ano — de modo que a demonstração funciona independentemente da data atual e não há nada sensível no repositório.
Esse padrão vale a pena adotar em suas próprias suítes de teste. Um certificado de teste comprometido expira eventualmente, e quando isso ocorre a falha se parece exatamente com o bug que esta versão foi criada para expor.
Conclusão
Três mudanças, uma direção: falhas que antes apareciam no destinatário agora aparecem no remetente. Renove o certificado em vez de usar AllowExpired, deixe o SHA‑256 como padrão, verifique documentos recebidos criptograficamente e escolha um nível de log antes de precisar dele. O exemplo executa todos os seis comportamentos em uma única passagem, incluindo a rejeição, para que a atualização possa ser ensaiada em alguns minutos.