💡 Exemplo completo em funcionamento disponível no GitHub:
pdf-signing-certificate-checks-python

Introdução

Um serviço assina PDFs enviados todas as noites. Numa manhã, o certificado que ele usa ultrapassa a data de validade, e nada parece mudar: o trabalho é executado, os arquivos são gravados, o log parece normal. Semanas depois alguém abre um desses documentos no Acrobat e vê uma faixa de aviso, porque uma assinatura feita com um certificado expirado não é uma assinatura mais fraca – é uma assinatura que os validadores relatam como inválida. Os documentos que parecem aprovados valem menos que os não assinados, porque as pessoas acreditaram neles.

Essa recusa tem um nome. A verificação de validade de certificado é um comportamento do GroupDocs.Signature para Python que recusa assinar quando o período de validade do certificado expirou, ou ainda não começou. Ela chegou na versão 26.9 junto com duas mudanças de mesma natureza: SHA‑256 tornou‑se o algoritmo de resumo padrão para assinaturas PDF, e SignatureSettings.log_level passou a filtrar em vez de ser silenciosamente ignorado. Cada uma transforma um resultado que antes acontecia silenciosamente e o coloca à sua frente.

Este artigo compara esses três controles conforme se comportam do Python via .NET – o que cada um altera na saída, quando utilizá‑los e quais dois detalhes da ligação custaram a alguém uma tarde. Cada resultado citado vem da execução do exemplo contra um PDF de uma página.

Por que isso importa mais do que uma nota de versão

As três mudanças compartilham uma propriedade que vale a pena nomear: todas convertem uma falha que você descobriria depois em uma que você descobre agora.

  • Certificados expirados: a chamada de assinatura falha onde alguém pode renovar o certificado, em vez de produzir documentos que falham na validação após a distribuição
  • Padrões de resumo: novas assinaturas usam SHA‑256 sem que ninguém precise lembrar de solicitar, de modo que a opção fraca requer uma decisão ao invés de descuido
  • Níveis de log: um serviço que configura apenas avisos agora recebe apenas avisos, o que torna os avisos legíveis, o que significa que eles são lidos

Esse último ponto é menos cosmético do que parece. Todo o valor do aviso de certificado expirado está em alguém vê‑lo, e um aviso enterrado entre dez mensagens de rastreamento por execução de assinatura é um aviso que ninguém vê.

Pré‑requisitos

Antes de começar, certifique‑se de que você tem:

  • Python 3.9 ou superior em um interpretador de 64 bits – o pacote inclui um runtime .NET empacotado e não possui roda de 32 bits
  • GroupDocs.Signature para Python via .NET 26.10.0, com uma licença temporária gratuita se quiser remover os limites de avaliação
  • Um PDF para assinar, e o pacote cryptography se quiser gerar certificados de teste descartáveis como o exemplo faz

Instalação

pip install groupdocs-signature-net cryptography

Controle 1 – O resumo gravado na assinatura

hash_algorithm em DigitalSignOptions escolhe o algoritmo de resumo. O padrão desde a 26.9 é SHA‑256, no formato adbe.pkcs7.detached que os validadores atuais esperam; antes disso, novas assinaturas eram SHA‑1.

with signature.Signature(source_path) as sign:
    options = DigitalSignOptions()
    options.certificate_stream = io.BytesIO(pfx)
    options.password = PASSWORD
    options.hash_algorithm = HashAlgorithm.SHA512
    options.reason = "Approved"

    result = sign.sign(output_path, options)
    return len(result.succeeded)

Dois detalhes merecem destaque. O certificado chega através de certificate_stream como um io.BytesIO em vez de um caminho de arquivo, que é a forma como um PKCS#12 construído na memória chega à biblioteca sem jamais ser gravado em disco – o exemplo depende disso para não enviar nenhuma chave privada. E HashAlgorithm oferece AUTO, SHA1, SHA256, SHA384 e SHA512, onde um carimbo de tempo, se você o adicionar, usa o mesmo algoritmo de resumo que a assinatura utilizou.

Na prática, este é o controle que você menos toca. O padrão já é a resposta correta, SHA384 e SHA512 existem para quando uma política de assinatura os nomeia, e SHA1 é uma configuração de compatibilidade para validadores que você não pode mudar.

Controle 2 – Se um certificado fora da validade impede a assinatura

Sem sobrescritas, assinar com um certificado cujo período de validade terminou – ou ainda não começou – gera GroupDocsSignatureException e não grava nada.

try:
    sign.sign(output_path, options)
    return True
except signature.GroupDocsSignatureException as error:
    print(f"Rejected: {str(error).splitlines()[0]}")
    return False

A mensagem indica o certificado, a data de expiração, sua impressão digital e a propriedade que o permitiria, o que basta para que uma aplicação informe ao operador o que renovar. Capturar apenas a primeira linha importa especificamente em Python: o texto da exceção continua com o rastreamento de pilha .NET por trás da ligação, e isso não deve ser exibido ao usuário.

Quando você realmente precisa assinar mesmo assim – um teste contra um certificado arquivado, ou um lote que deve ser executado esta noite enquanto a renovação está em andamento – a sobrescrita é feita por chamada:

settings = signature.SignatureSettings(ConsoleLogger())
settings.log_level = LogLevel.WARNING | LogLevel.ERROR

with signature.Signature(source_path, settings=settings) as sign:
    options.allow_expired = True
    result = sign.sign(output_path, options)

allow_not_yet_valid tem a mesma forma para um certificado emitido para data futura, e as duas bandeiras são independentes: permitir um certificado expirado não permite um ainda não válido. Um certificado antecipado geralmente indica que o relógio da máquina está errado, e um relógio errado torna todas as assinaturas produzidas por aquela máquina questionáveis, portanto verifique isso antes de sobrescrever qualquer coisa.

Ambas as sobrescritas emitem um aviso em vez de passar silenciosamente, que é a parte que se conecta ao terceiro controle.

Controle 3 – Se alguém descobre

SignatureSettings.log_level é um valor de bandeiras. O exemplo assina o mesmo documento três vezes, sob LogLevel.NONE, LogLevel.WARNING | LogLevel.ERROR e LogLevel.ALL, contando o que chega:

logger = CollectingLogger()
settings = signature.SignatureSettings(logger)
settings.log_level = level

with signature.Signature(source_path, settings=settings) as sign:
    options.allow_expired = True
    sign.sign(output_path, options)

As contagens resultam em nada, depois um aviso, depois esse aviso mais dez rastreamentos. Antes da 26.9 todas as três linhas seriam idênticas, porque o nível era aceito e ignorado – o que vale a pena saber se você já definiu um, não viu mudança e concluiu que leu seu próprio código errado.

Dois detalhes da ligação me custaram uma tarde, então vale declará‑los claramente. SignatureSettings.logger é somente leitura, portanto o logger é passado como argumento do construtor e atribuí‑lo gera AttributeError; log_level é definido normalmente depois. E um logger customizado não deve herdar de groupdocs.signature.logging.ILogger – essa classe base envolve um objeto nativo cujo construtor precisa de um identificador que a biblioteca possui, de modo que a herança gera TypeError. A ligação aceita qualquer objeto simples que forneça os três métodos:

class StdlibLogger:
    def error(self, message, exception=None):
        logging.getLogger("groupdocs").error(message, exc_info=exception)

    def warning(self, message, exception=None):
        logging.getLogger("groupdocs").warning(message)

    def trace(self, message):
        logging.getLogger("groupdocs").debug(message)

Forneça a error e a warning um parâmetro opcional exception. A biblioteca nem sempre passa um, e um logger que o exige falha nas mensagens que o omitem.

Comparando os três: Quando usar cada um

Controle Melhor para Principais vantagens Limitações
hash_algorithm atender a uma política que nomeia um algoritmo de resumo uma única atribuição; mesmo tamanho de saída inútil se o próprio certificado for não confiável
verificação de validade e sobrescritas qualquer assinatura feita para outras pessoas a falha ocorre onde pode ser corrigida uma sobrescrita produz um arquivo, mas não um confiável
log_level serviços cujos logs já estão ocupados onze mensagens tornam‑se uma filtra apenas o log, nunca as exceções

Eles não são alternativas – uma única chamada de assinatura usa os três. A ordem para pensá‑los é a ordem de consequência: a verificação de validade decide se o arquivo existe, o algoritmo de resumo decide o que há dentro dele, e o nível de log decide quem sabe.

O nível de log altera quais exceções eu recebo?

Não. Ele decide quais mensagens chegam ao seu logger e nada mais. Um certificado expirado ainda gera GroupDocsSignatureException sob LogLevel.NONE, e allow_expired ainda assina sob LogLevel.ALL; valores de retorno e exceções são idênticos em todos os níveis. O que muda é se o aviso que explica uma assinatura questionável é lido por alguém.

Verificação movida na mesma direção

Vale mencionar porque é a outra metade da mesma liberação. verify com um DigitalVerifyOptions vazio agora verifica criptograficamente cada assinatura digital de PDF, de modo que um documento alterado após a assinatura volta a ser inválido em vez de apenas inexplicado:

with signature.Signature(signed_path) as sign:
    result = sign.verify(DigitalVerifyOptions())
    return result.is_valid

Duas linhas, e vale acrescentar a qualquer pipeline que assine e depois armazene. Observe o que um True não promete: ele indica que a assinatura corresponde ao documento, não que o emissor seja confiável. Os certificados autoassinados do exemplo são verificados aqui e ainda são recusados por um leitor de PDF, o que responde à questão de confiança separadamente.

Melhores práticas e dicas

  • Mantenha a recusa como padrão em tudo que assina em nome dos usuários, e sobrescreva por chamada em vez de globalmente. A exceção é barata; um lote de assinaturas inválidas não é.
  • Registre o texto do aviso, não apenas um contador. Ele nomeia o certificado e a data, que é a única parte que um operador pode agir.
  • Verifique o relógio antes de permitir um certificado ainda não válido. O certificado geralmente está correto e a máquina está errada, e isso afeta mais de uma chamada de assinatura.
  • Mantenha rastreamentos fora da produção. Cerca de dez por execução de assinatura acumulam rapidamente; ative‑os ao diagnosticar e desative‑os depois.
  • Verifique após assinar em qualquer pipeline, agora que a verificação é criptográfica, para que uma saída corrompida seja detectada antes que o destinatário a encontre.

Conclusão

Três controles, uma chamada de assinatura, e a mesma ideia de design por trás de todos eles: o resultado arriscado agora requer uma decisão, e o seguro não requer nada. Mantenha a verificação de validade, trate allow_expired como uma exceção por chamada que você registra, deixe o algoritmo de resumo como está a menos que uma política diga o contrário, e defina um nível de log que torne os avisos legíveis.

Executar o exemplo contra um dos seus próprios PDFs leva um minuto e imprime exatamente o que cada controle alterou – seis arquivos assinados, uma recusa deliberada e três linhas de contagem de mensagens que não são mais iguais.

Recursos adicionais