Signum.Pdf.Validator
1.0.2
See the version list below for details.
dotnet add package Signum.Pdf.Validator --version 1.0.2
NuGet\Install-Package Signum.Pdf.Validator -Version 1.0.2
<PackageReference Include="Signum.Pdf.Validator" Version="1.0.2" />
<PackageVersion Include="Signum.Pdf.Validator" Version="1.0.2" />
<PackageReference Include="Signum.Pdf.Validator" />
paket add Signum.Pdf.Validator --version 1.0.2
#r "nuget: Signum.Pdf.Validator, 1.0.2"
#:package Signum.Pdf.Validator@1.0.2
#addin nuget:?package=Signum.Pdf.Validator&version=1.0.2
#tool nuget:?package=Signum.Pdf.Validator&version=1.0.2
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
- Compatibilidad
- Instalación
- Primeros pasos
- Referencia de la API
- Formatos de cédula
- Alcance de la validación
- Casos de uso
- Errores
- Rendimiento y concurrencia
- Preguntas frecuentes
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,YaFirmoPorNombreyBuscarFirmasexcluyen los sellos de tiempo: un sello nunca cuenta como que alguien firmó.LeerFirmassí los devuelve, marcados conEsDocTimeStamp.
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
SignumValidatorno tiene estado interno: es seguro entre hilos y conviene registrarlo como singleton.LeerFirmastrabaja 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.YaFirmoyBuscarFirmasllaman internamente aLeerFirmas. 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
Historial
1.0.2 — Tres correcciones de lectura:
YaFirmo con la cédula en blanco respondía true (falso positivo si el dato venía vacío de la
base); las firmas con signingTime entre los atributos no firmados se rechazaban aunque son
válidas; y la clasificación de firmas: una firma que quedaba pegada a un sello de tiempo
(habitual en facturas electrónicas) se leía como sello y se reportaba no íntegra.
1.0.1 — Corrige un defecto de la 1.0.0: una firma cuyo CMS terminaba en un dígito hexadecimal
0 (≈1 de cada 16) se leía como no íntegra y sin firmante, así que YaFirmo podía devolver
false para alguien que sí había firmado. Actualizar es recomendable.
Licencia
MIT. Copyright (c) Andrey Salazar.
| Product | Versions 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. |
-
.NETFramework 4.0
- Portable.BouncyCastle (>= 1.9.0)
- System.ValueTuple (>= 4.5.0)
-
.NETFramework 4.5
- Portable.BouncyCastle (>= 1.9.0)
- System.ValueTuple (>= 4.5.0)
-
.NETFramework 4.6.1
- BouncyCastle.Cryptography (>= 2.6.2)
- System.ValueTuple (>= 4.5.0)
-
.NETStandard 2.0
- BouncyCastle.Cryptography (>= 2.6.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.