Signum.Pdf.Validator 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Signum.Pdf.Validator --version 1.0.0
                    
NuGet\Install-Package Signum.Pdf.Validator -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Signum.Pdf.Validator" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Signum.Pdf.Validator" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Signum.Pdf.Validator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Signum.Pdf.Validator --version 1.0.0
                    
#r "nuget: Signum.Pdf.Validator, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Signum.Pdf.Validator@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Signum.Pdf.Validator&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Signum.Pdf.Validator&version=1.0.0
                    
Install as a Cake Tool

Signum.Pdf.Validator

Lectura y validación de firmas digitales en PDF para .NET Framework 4.0 a 4.8, y también para .NET Core / .NET 5+.

Responde las preguntas que una aplicación necesita hacerse sobre un documento firmado — ¿ya firmó esta persona?, ¿quiénes firmaron?, ¿lo modificaron después de firmar? — sin depender de componentes nativos, sin llamadas de red y sin instalar nada en el servidor.

var validador = new SignumValidator("tu-clave-de-activacion");

if (validador.YaFirmo(pdf, "1-1234-5678"))
    return "Este documento ya fue firmado por esa persona.";

Contenido


Por qué existe

La familia Signum tiene tres paquetes. Este es el único que llega a plataformas antiguas:

Paquete Qué hace Plataforma mínima
Signum.Pdf Firmar + validar .NET 8
Signum.Pdf.NetFramework Firmar + validar .NET Framework 4.6.1
Signum.Pdf.Validator Solo validar .NET Framework 4.0

Los dos primeros no bajan de 4.6.1 porque el motor de firmado usa PDFsharp, que solo publica netstandard2.0. Pero leer firmas no necesita PDFsharp: se localizan a nivel de bytes por su /ByteRange (ISO 32000-2 §12.8.1) y se verifican con BouncyCastle. Sin esa dependencia, el piso baja a 4.0.

Se instala como Signum.Pdf.Validator y el ensamblado es Signum.Pdf.Validator.dll.

Es un paquete independiente: no referencia a los otros ni comparte binarios con ellos.

No instalar junto con Signum.Pdf ni Signum.Pdf.NetFramework. Los tipos públicos se llaman igual (FirmaExistente, SignumException) y el compilador no sabría cuál usar. Si ya usás alguno de esos, ya tenés estos métodos con los mismos nombres y el mismo comportamiento.

Compatibilidad

Tu proyecto Ensamblado que se instala Dependencias
.NET Framework 4.0 lib/net40 Portable.BouncyCastle 1.9, System.ValueTuple
.NET Framework 4.5 – 4.6.0 lib/net45 Portable.BouncyCastle 1.9, System.ValueTuple
.NET Framework 4.6.1 – 4.8.x lib/net461 BouncyCastle.Cryptography 2.6, System.ValueTuple
.NET Core 2.0+, .NET 5 – 10 lib/netstandard2.0 BouncyCastle.Cryptography 2.6

El mínimo es .NET Framework 4.0. Debajo de eso no se puede: el código usa LINQ, que nace en 3.5, y habría que bajar además a la línea 1.8 de BouncyCastle.

Los métodos devuelven ReadOnlyCollection<FirmaExistente> y no IReadOnlyList<T> precisamente para llegar a 4.0, donde esa interfaz todavía no existe. En 4.5 y superior no se nota la diferencia: ReadOnlyCollection<T> ya implementa IReadOnlyList<T>, así que esto sigue compilando igual:

IReadOnlyList<FirmaExistente> firmas = validador.LeerFirmas(pdf);   // 4.5+

No hay dependencias nativas, ni COM, ni registro de componentes: se copia el .dll y funciona.

Instalación

Install-Package Signum.Pdf.Validator
dotnet add package Signum.Pdf.Validator

Primeros pasos

El validador no tiene estado. Una sola instancia sirve para toda la aplicación y se puede usar desde varios hilos a la vez, así que conviene guardarla:

using Signum;

public static class Firmas
{
    public static readonly ISignumValidator Validador =
        new SignumValidator(ConfigurationManager.AppSettings["Signum:ClaveActivacion"]);
}

Con inyección de dependencias — el paquete no obliga a ningún contenedor, justamente porque las aplicaciones a las que apunta suelen no tener uno:

// ASP.NET Core
services.AddSingleton<ISignumValidator>(_ => new SignumValidator(clave));

// Unity
container.RegisterInstance<ISignumValidator>(new SignumValidator(clave));

Referencia de la API

Todo el paquete son cuatro métodos sobre ISignumValidator.

bool YaFirmo(byte[] pdf, string cedula)

La pregunta central. Compara contra el SERIALNUMBER del certificado —no contra el texto de la estampa, que es solo un dibujo— y tolera cualquier formato de cédula.

validador.YaFirmo(pdf, "1-1234-5678");   // true

bool YaFirmoPorNombre(byte[] pdf, string nombreParcial)

Búsqueda parcial sobre el CN del certificado, estilo LIKE '%…%'. Ignora mayúsculas, tildes y espacios de más.

validador.YaFirmoPorNombre(pdf, "salazar guzman");   // true
validador.YaFirmoPorNombre(pdf, "SALAZAR  GUZMÁN");  // true, lo mismo

ReadOnlyCollection<FirmaExistente> LeerFirmas(byte[] pdf)

Todas las firmas del documento, en el orden en que aparecen.

foreach (var firma in validador.LeerFirmas(pdf))
{
    if (firma.EsDocTimeStamp) continue;   // sello de tiempo, no es una persona

    Console.WriteLine($"{firma.Firmante} ({firma.Cedula})");
    Console.WriteLine($"  fecha oficial : {firma.FechaOficial:dd/MM/yyyy HH:mm}");
    Console.WriteLine($"  íntegra       : {firma.Integra}");
}

ReadOnlyCollection<FirmaExistente> BuscarFirmas(byte[] pdf, string cedula = null, string nombre = null)

Filtra por cédula, por nombre o por ambos. Los dos parámetros son opcionales; sin ninguno devuelve todas las firmas de personas.

var deEsaPersona = validador.BuscarFirmas(pdf, cedula: "1-1234-5678");
var losSalazar   = validador.BuscarFirmas(pdf, nombre: "salazar");

YaFirmo, YaFirmoPorNombre y BuscarFirmas excluyen los sellos de tiempo: un sello nunca cuenta como que alguien firmó. LeerFirmas sí los devuelve, marcados con EsDocTimeStamp.

FirmaExistente

Propiedad Tipo Qué es
Firmante string Nombre común (CN) del certificado
Cedula string SERIALNUMBER del certificado. En Costa Rica, la cédula (CPF-01-1234-5678)
Emisor string CN de la autoridad que emitió el certificado
NombreReconocimiento string Nombre distinguido completo del firmante
FechaFirma DateTime? Hora que declaró el firmante. La pone su propia máquina: es informativa
FechaOficial DateTime? Hora certificada por la autoridad de sellado. Es la que tiene valor
Integra bool La firma corresponde a esos bytes y a ese certificado
CubreTodoElDocumento bool Si el rango firmado llega hasta el final del archivo
SubFilter string ETSI.CAdES.detached, adbe.pkcs7.detached, ETSI.RFC3161
EsDocTimeStamp bool Es un sello de tiempo de documento, no la firma de una persona
CampoNombre string Nombre del campo de firma dentro del PDF

Sobre las dos fechas. FechaFirma la escribe la máquina del firmante y se puede adulterar cambiando el reloj. FechaOficial viene del sello de tiempo RFC 3161 emitido por la autoridad. Para plazos, vencimientos o cualquier cosa con consecuencia legal, usar FechaOficial.

Sobre CubreTodoElDocumento. Que sea false no significa que el documento esté adulterado: es lo normal cuando alguien firmó después, o cuando se agregó el sello de tiempo. Significa que hay contenido posterior a esa firma en particular.

Formatos de cédula

YaFirmo y BuscarFirmas normalizan antes de comparar. Todas estas formas encuentran a la misma persona:

CPF-01-1234-5678      01-1234-5678      0112345678
1-1234-5678           1 1234 5678       112345678

Se ignoran el prefijo (CPF-, CPJ-), los guiones, los espacios y los ceros a la izquierda. En la práctica: se puede pasar la cédula tal como está guardada en la base de datos, sin limpiarla.

Alcance de la validación

Integra responde una pregunta concreta y acotada: la firma corresponde a esos bytes y a ese certificado. Si alguien cambió una coma después de firmar, sale false.

Lo que no hace, deliberadamente:

  • no evalúa si el certificado es de confianza,
  • no consulta si estaba revocado al momento de firmar,
  • no verifica la cadena hasta la raíz nacional,
  • no emite un dictamen legal.

Eso es trabajo de un validador acreditado. El PDF firmado se pasa por el validador nacional y ese es el veredicto que vale. Esta librería es para la lógica de la aplicación: decidir si a alguien le toca firmar o si ya lo hizo, mostrar quiénes firmaron, bloquear un flujo hasta que estén todas las firmas.

No abre puertos, no consulta OCSP ni CRL, no descarga nada: todo sale del propio archivo. Un servidor sin salida a internet la corre igual.

Casos de uso

Bloquear una firma duplicada

if (validador.YaFirmo(pdf, usuario.Cedula))
    throw new InvalidOperationException("Ya firmaste este documento.");

Saber a quién falta

var pendientes = requeridos
    .Where(c => !validador.YaFirmo(pdf, c.Cedula))
    .ToList();

if (pendientes.Count == 0)
    expediente.MarcarCompleto();

Mostrar el estado en pantalla

var firmas = validador.LeerFirmas(pdf)
    .Where(f => !f.EsDocTimeStamp)
    .Select(f => new
    {
        f.Firmante,
        f.Cedula,
        Fecha = f.FechaOficial ?? f.FechaFirma,
        Estado = f.Integra ? "Válida" : "Alterada",
    })
    .ToList();

Detectar un documento manipulado

var alteradas = validador.LeerFirmas(pdf).Where(f => !f.Integra).ToList();
if (alteradas.Any())
    bitacora.Advertir($"El documento {id} tiene {alteradas.Count} firma(s) que no verifican.");

Errores

Situación Qué pasa
Clave de activación ausente o inválida El constructor lanza SignumActivacionException
pdf es null ArgumentNullException
El archivo no es un PDF, o no tiene ninguna firma Devuelve lista vacía; YaFirmo devuelve false. No lanza
Una firma del documento está corrupta o es ilegible Esa firma sale con Integra = false; las demás se leen igual

Una firma rota nunca tumba la lectura de las otras: es la diferencia entre "no puedo mostrar nada" y "esta de acá no verifica".

Rendimiento y concurrencia

  • SignumValidator no tiene estado interno: es seguro entre hilos y conviene registrarlo como singleton.

  • LeerFirmas trabaja sobre el arreglo de bytes en memoria; el costo va con el tamaño del archivo. Para documentos grandes que se consultan seguido, conviene guardar el resultado en caché en vez de releer el PDF en cada petición.

  • YaFirmo y BuscarFirmas llaman internamente a LeerFirmas. Si vas a preguntar por varias cédulas sobre el mismo documento, leé una vez y filtrá:

    var firmas = validador.LeerFirmas(pdf);   // una sola pasada
    var quienes = firmas.Where(f => !f.EsDocTimeStamp).Select(f => f.Cedula).ToHashSet();
    

Preguntas frecuentes

¿Sirve para PDFs firmados con otras herramientas? Sí. Lee cualquier firma estándar del PDF (adbe.pkcs7.detached, ETSI.CAdES.detached), la haya puesto Adobe Acrobat, el firmador del BCCR, iText u otro.

¿Y para firmas de otros países? Sí. El formato PAdES es la norma europea ETSI EN 319 142, adoptada en varios países. Cedula es el SERIALNUMBER del certificado, así que su contenido depende de cada autoridad emisora — la normalización de formato está pensada para el esquema costarricense.

¿Puedo firmar con este paquete? No. Para firmar están Signum.Pdf (.NET 8+) y Signum.Pdf.NetFramework (4.6.1+).

¿Necesito el certificado del firmante para validar? No. El certificado viene dentro del propio PDF; por eso no hace falta red ni almacén de certificados.

¿Funciona en un servidor sin internet? Sí, sin ninguna limitación.

¿Y en Windows Server 2008 R2 / IIS 7.5? Sí, siempre que tenga .NET Framework 4.0 o superior.

Clave de activación

El constructor exige una clave válida; sin ella lanza SignumActivacionException. Para solicitar una: andreyclemen@gmail.com

Licencia

MIT. Copyright (c) Andrey Salazar.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net40 is compatible.  net403 was computed.  net45 is compatible.  net451 was computed.  net452 was computed.  net46 was computed.  net461 is compatible.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.3 98 8/4/2026
1.0.2 91 8/2/2026
1.0.1 109 8/2/2026 1.0.1 is deprecated because it is no longer maintained and has critical bugs.
1.0.0 112 7/31/2026 1.0.0 is deprecated because it is no longer maintained and has critical bugs.