💡 Ejemplo completo en funcionamiento disponible en GitHub:
digital-signing-certificate-validity-dotnet
El problema de cumplimiento que nadie ve hasta que lo detecta un auditor
Un servicio de firma funciona durante tres años sin errores. Los documentos se envían, los destinatarios los aceptan, y nada en los registros sugiere un problema. Entonces el validador de una contraparte marca un lote como inválido, y la investigación revela dos causas: las firmas se generaron con SHA‑1 y, durante los últimos cuatro meses, el certificado había expirado.
Ambas fallas fueron silenciosas en el momento de la firma. Eso es lo que cambia GroupDocs.Signature 26.9.
La aplicación de la validez del certificado es el nuevo comportamiento predeterminado para la firma digital en .NET: un certificado fuera de su ventana de validez es rechazado en lugar de ser usado. Llega con dos acompañantes — SHA‑256 como algoritmo de resumen predeterminado para PDF, y un LogLevel que finalmente filtra — y, juntos, trasladan tres clases de fallas del destinatario al remitente, donde aún pueden corregirse.
Por qué el éxito silencioso es el resultado costoso
Firmar es inusual porque la parte que comete el error no es la que lo descubre. Una factura malformada falla en tu propio sistema; una firma inválida falla en el sistema de otro, semanas después, sin diagnóstico que puedas leer.
Esa asimetría es la razón por la que “la API devolvió éxito” no es una garantía útil aquí. Los valores predeterminados antiguos estaban optimizados para no interrumpir al llamador, y el costo recaía en el destinatario y, eventualmente, en quien tuvo que volver a firmar y reenviar varios cientos de documentos.
Cambio 1: Los certificados expirados son rechazados
El cambio principal. Sign ahora lanza GroupDocsSignatureException cuando la validez del certificado ha terminado o aún no ha comenzado, y no se escribe nada en disco.
try
{
signature.Sign(outputPath, options);
return true;
}
catch (GroupDocsSignatureException ex)
{
Console.WriteLine($" Rejected: {ex.Message}");
return false;
}
El mensaje nombra el certificado y la propiedad que lo permitiría, de modo que un operador que lea una línea del registro pueda actuar sin abrir la documentación. Para una canalización que actualiza a 26.9 y comienza a fallar, esta es casi siempre la razón, y la respuesta correcta es renovar, no suprimir.
Cuando realmente necesitas el comportamiento antiguo, es una sola propiedad:
var options = new DigitalSignOptions(certificate)
{
Password = certificatePassword,
AllowExpired = true
};
El documento se firma y se envía una advertencia al registrador. Los validadores siguen rechazando el resultado, porque AllowExpired controla lo que la biblioteca permite, no lo que vale el certificado. La bandera acompañante AllowNotYetValid cubre el otro extremo de la ventana y es deliberadamente independiente: permitir un certificado expirado no permite silenciosamente uno con fecha futura.
Cambio 2: SHA‑256 por defecto
Las firmas digitales PDF ahora se escriben con SHA‑256 en el formato adbe.pkcs7.detached que los validadores actuales esperan. Las versiones anteriores usaban SHA‑1.
var options = new DigitalSignOptions(certificate)
{
Password = certificatePassword,
HashAlgorithm = HashAlgorithm.Sha256,
Reason = "Approved",
Location = "Head office"
};
Establecer la propiedad explícitamente solo es necesario para ir más allá — Sha384 o Sha512 cuando una política los requiere — o para permanecer en Sha1 para un validador que no pueda manejar otra cosa. Una marca de tiempo añadida a la firma usa el mismo resumen.
La verificación cambió en la misma versión y en la misma dirección: DigitalVerifyOptions sin criterios solía ser casi un no‑op, y ahora realiza una comprobación criptográfica completa, de modo que un documento alterado después de la firma se reporta como inválido.
Cambio 3: LogLevel realmente filtra
SignatureSettings ha aceptado un registrador desde hace tiempo. Antes de la 26.9 el nivel se ignoraba, por lo que cada mensaje llegaba sin importar y la mayoría de los servicios desactivaban el registro en lugar de ahogarse en trazas.
El ejemplo hace que la diferencia sea medible firmando el mismo documento tres veces con un registrador contador:
var levels = new Dictionary<string, LogLevel>
{
["None"] = LogLevel.None,
["Warning | Error"] = LogLevel.Warning | LogLevel.Error,
["All"] = LogLevel.All
};
None produce cero mensajes, Warning | Error mantiene la única advertencia generada por el certificado expirado permitido, y All añade una traza por paso. El registrador contador es el punto de integración para tu propia pila:
public void Warning(string message)
{
Warnings++;
WarningMessages.Add(message);
}
Implementa esos tres métodos con Serilog, NLog o Application Insights y los diagnósticos de la biblioteca llegarán a donde el resto de los registros de tu servicio se escriben.
¿Cambia el nivel de registro las excepciones que recibo?
No, y vale la pena ser explícitos porque los dos conceptos parecen relacionados. LogLevel filtra lo que llega al ILogger. Las excepciones se lanzan a tu código de todos modos: un certificado expirado sin AllowExpired sigue lanzando en LogLevel.None, y tu bloque catch se comporta idénticamente. Diagnósticos y flujo de control son canales separados, lo que permite ejecutar producción con Warning | Error de forma segura.
El rechazo es más barato de lo que parece
La objeción a una parada dura es operativa: un lote nocturno que antes terminaba ahora falla a las 02:00 y alguien recibe una alerta. Ese es un costo real, y sigue siendo el menor. Un lote rechazado genera una alerta, una renovación y una re‑ejecución, todo dentro de tus propios sistemas. Un lote firmado con un certificado expirado lo descubre el destinatario, lo que implica un hilo de soporte, una re‑emisión de cada documento afectado y una conversación incómoda sobre cuánto tiempo había estado ocurriendo.
El ejemplo hace que la falla sea concreta y no teórica: firma intencionalmente con un certificado expirado, captura la excepción y muestra el mensaje, de modo que puedas ver exactamente qué contendrán tus registros antes de que la actualización llegue a producción. Te recomendaría ejecutar ese método contra tu propio almacén de certificados antes de programar el salto de versión.
Qué hacer antes de actualizar
Tres comprobaciones, en orden de probabilidad de que te afecten.
- Revisa la expiración de certificados en cada ruta de firma, incluidas las que se ejecutan mensualmente o trimestralmente — ahí es donde un certificado expirado se oculta más tiempo.
- Busca
HashAlgorithm: si nada lo establece, tus resúmenes cambiarán de SHA‑1 a SHA‑256 al actualizar, lo cual es una mejora que aún debe aparecer en las notas de la versión. - Decide deliberadamente un nivel de registro. El valor predeterminado honesto para un servicio es
Warning | Error;Allsirve para reproducir un problema específico, yNonesignifica renunciar a la única señal que indica que una firma se realizó bajo una exención.
La verificación cambió en la misma dirección
Es fácil pasarlo por alto, porque el código que llama no tiene que cambiar. DigitalVerifyOptions sin criterios establecidos solía estar cerca de un no‑op: comparaba los criterios que se le daban, y al no haber ninguno, tenía poco que decir. Desde la 26.9 la misma llamada realiza una comprobación criptográfica completa de cada firma digital PDF.
Para un servicio que verifica documentos entrantes, eso es una actualización silenciosa de “hay una firma aquí” a “esta firma coincide con este contenido”. Vale la pena saberlo antes de ver que un documento empieza a fallar la verificación que pasó el mes pasado: probablemente el documento fue alterado y la comprobación anterior simplemente no lo detectó.
Los certificados en el ejemplo
Un detalle que vale la pena copiar más que el código: el ejemplo no incluye una clave privada. TestCertificates.cs genera tres PFX autofirmados en memoria en tiempo de ejecución — válido, expirado el año pasado, válido a partir del próximo año — de modo que la demostración funciona sea cual sea la fecha actual y no haya nada sensible en el repositorio.
Ese patrón es útil para adoptarlo en tus propias suites de pruebas. Un certificado de prueba comprometido expirará eventualmente, y cuando lo haga la falla se verá exactamente como el error que esta versión pretende exponer.
Conclusión
Tres cambios, una dirección: fallas que antes aparecían en el destinatario ahora aparecen en el remitente. Renueva el certificado en lugar de usar AllowExpired, deja que SHA‑256 sea el predeterminado, verifica criptográficamente los documentos entrantes y elige un nivel de registro antes de que lo necesites. El ejemplo ejecuta los seis comportamientos en una sola pasada, incluido el rechazo, de modo que la actualización pueda ensayarse en un par de minutos.