Signum.Pdf.NetFramework
1.1.0
See the version list below for details.
dotnet add package Signum.Pdf.NetFramework --version 1.1.0
NuGet\Install-Package Signum.Pdf.NetFramework -Version 1.1.0
<PackageReference Include="Signum.Pdf.NetFramework" Version="1.1.0" />
<PackageVersion Include="Signum.Pdf.NetFramework" Version="1.1.0" />
<PackageReference Include="Signum.Pdf.NetFramework" />
paket add Signum.Pdf.NetFramework --version 1.1.0
#r "nuget: Signum.Pdf.NetFramework, 1.1.0"
#:package Signum.Pdf.NetFramework@1.1.0
#addin nuget:?package=Signum.Pdf.NetFramework&version=1.1.0
#tool nuget:?package=Signum.Pdf.NetFramework&version=1.1.0
Signum.Pdf
Firma digital PAdES para .NET, sin ataduras. Firma PDFs en PAdES-B-LTA —firma + sello de tiempo + validación a largo plazo— usando solo dependencias MIT. PAdES es un estándar ETSI de alcance internacional, así que la librería sirve para cualquier PKI; está probada contra la infraestructura de firma digital de Costa Rica (tarjetas SINPE/BCCR, TSA de SINPE).
Validado contra el validador nacional de Costa Rica. Un documento firmado con Signum pasa los cuatro checks en verde: integridad y autenticidad, validez en el tiempo, revocación contenida en el documento y fecha oficial de la firma sin advertencias. Probado en producción con tarjetas SINPE y la TSA del BCCR.
Límite conocido: las tablas de referencias cruzadas comprimidas (xref streams) se leen, pero las revisiones que agrega Signum se escriben siempre con tabla clásica.
- ✅ Los cuatro checks del validador nacional en verde
- ✅ Firma diferida (deferred signing): el PDF nunca sale del servidor; la tarjeta firma solo un hash de 32 bytes
- ✅ Firma directa con certificado del almacén/PFX (sellos institucionales)
- ✅ PAdES-B-LTA completo: CAdES + sello de tiempo de la firma (CAdES-T) + DSS con OCSP/CRL embebidos + Document Timestamp
- ✅ Estampa visible configurable estilo Adobe (o firma invisible)
- ✅ Lotes con id/guid propio y aislamiento de errores por documento
- ✅ Lectura de firmas existentes: ¿quién firmó? ¿ya firmó este usuario?
- ✅ Licencia MIT — sin AGPL, sin regalías, sin sorpresas
- Dependencias: PDFsharp (MIT) + BouncyCastle.Cryptography (MIT)
Instalación
dotnet add package Signum.Pdf
Requiere .NET 8, 9 o 10.
Activación y registro
Signum se usa por inyección de dependencias y requiere una clave de activación:
// Program.cs
builder.Services.AddSignum(builder.Configuration["Signum:ClaveActivacion"]!, o =>
{
o.TsaUrl = "http://tsa.sinpe.fi.cr/tsahttp/"; // autoridad de sellado por defecto
o.Traza = m => logger.LogInformation("Signum: {M}", m); // diagnóstico opcional
});
// Uso: se inyecta ISignum — la única superficie pública del paquete
public sealed class MiServicio(ISignum signum) { /* … */ }
Sin una clave válida, AddSignum lanza SignumActivacionException en el arranque y no registra
nada. Si la firma es una función opcional de tu app, usá TryAddSignum, que devuelve false en
vez de lanzar.
Para solicitar una clave de activación, escribí a andreyclemen@gmail.com.
Inicio rápido
1. Firma diferida (tarjeta inteligente) — el caso típico
El flujo es en dos fases porque la llave privada vive en la tarjeta del usuario (en otra máquina) y solo puede firmar un hash:
// ───── FASE 1 (servidor): preparar ─────
var prep = signum.Firmar(pdfBytes)
.ConCadena(cadenaDer) // la CADENA de certificados de la tarjeta (ver nota abajo)
.ConEstampa(e => e
.EnPagina(1)
.Recuadro(x: 90, y: 350, ancho: 300, alto: 58)) // puntos PDF, origen abajo-izquierda
// el nombre NO se pasa: sale solo del certificado (CN). Look default = nombre grande |
// "Firmado digitalmente por … / Fecha: …"
.Preparar();
// prep.Digest -> LO QUE FIRMA LA TARJETA: la huella (32 bytes) del documento + sus atributos.
// Es lo ÚNICO que viaja al agente/tarjeta; el PDF nunca sale del servidor.
// prep.Estado -> LA MEMORIA DE LA OPERACIÓN: el PDF preparado + los datos exactos que Signum
// necesita para terminar. Guardalo donde quieras (memoria, BD, Redis) mientras
// el usuario digita el PIN, y devolvéselo a Completar en la fase 2.
// ───── (el agente local le pide el PIN a la tarjeta y firma prep.Digest) ─────
// ───── FASE 2 (servidor): completar ─────
byte[] pdfFirmado = signum.Completar(prep.Estado, firmaDeLaTarjeta)
.ConSelloDeTiempo("http://tsa.sinpe.fi.cr/tsahttp/") // CAdES-T: la "fecha oficial"
.ConLtv("http://tsa.sinpe.fi.cr/tsahttp/") // DSS + DocTimeStamp = B-LTA
.Emitir();
¿Qué es
ConCadena(cadenaDer)? En la firma diferida el certificado NO está en el servidor: vive en la tarjeta del usuario. El agente local lo lee (parte pública, sin PIN) y manda la cadena completa: certificado del firmante + CAs intermedias + raíz, cada uno en DER (byte[]). Signum la necesita para: (1) meter los certificados dentro de la firma (el check "jerarquía de confianza"), (2) sacar el nombre/cédula del firmante, y (3) el LTV (a quién pedirle OCSP/CRL). En la firma directa no existe:ConCertificado(x509)ya trae todo.
2. Firma directa (sello institucional, PFX/almacén)
byte[] sellado = signum.Firmar(pdfBytes)
.ConCertificado(certificadoConLlave) // X509Certificate2 (almacén de Windows o PFX)
.ConEstampa(e => e
.EnPagina(1).Recuadro(246, 76, 300, 48)
.SinGrafico()
.Firmante("TU INSTITUCION (SELLO ELECTRONICO)")
.MostrarMotivo("Sello electrónico institucional"))
.ConSelloDeTiempo(tsaUrl)
.ConLtv(tsaUrl)
.Emitir();
3. Firma invisible
No llames ConEstampa — la firma queda en el PDF sin marca visual:
var prep = signum.Firmar(pdfBytes).ConCadena(cadenaDer).Preparar();
La estampa visible
Se configura igual que el diálogo "Configurar aspecto de la firma" de Adobe Acrobat:
.ConEstampa(e => e
.EnPagina(1)
.Recuadro(90, 350, 300, 58)
.ConLogotipo(pngLogoDeTuEmpresa, opacidad: 0.12f)) // marca de agua tenue (opcional)
// Con SOLO eso, la estampa sale con el look por defecto (dos columnas estilo Adobe):
//
// JOSE ANDREY SALAZAR ⟨logo⟩ Firmado digitalmente por JOSE
// GUZMAN (FIRMA) tenue ANDREY SALAZAR GUZMAN (FIRMA)
// Fecha: 2026.07.27 11:20:13 -06'00'
//
// El nombre sale del CERTIFICADO (CN) — no hay que pasarlo. Texto auto-ajustado al recuadro.
// ── Opciones (todas opcionales, estilo diálogo de Adobe) ──
// GRÁFICO (columna izquierda) — elegí UNO:
// .GraficoNombre() // default: el nombre en grande
// .GraficoImagen(pngFirmaManuscrita) // tu firma escaneada (PNG con transparencia)
// .SinGrafico() // solo el bloque de texto
// TEXTO (columna derecha):
// .Firmante("OTRO NOMBRE") // override (default: CN del certificado)
// .MostrarFecha() // ON por default (reloj del servidor; informativa)
// .MostrarUbicacion("San José, Costa Rica")
// .MostrarMotivo("Aprobación del documento")
// .MostrarNombreReconocimiento() // el DN completo del certificado
// .MostrarVersion() // "Signum x.y"
// .ConEtiquetas(false) // quita los prefijos "Fecha:", "Motivo:"…
Notas:
- El texto se auto-ajusta al recuadro: dibujes el rectángulo grande o chico, el nombre y la fecha SIEMPRE salen completos.
MostrarFechausa el reloj del servidor al preparar — es informativa, igual que en Adobe. La fecha oficial (la que reporta el validador) es la del sello de tiempo del TSA (ConSelloDeTiempo), que es criptográfica y no depende de ningún reloj local.- Signum no trae ningún logo embebido: el logotipo y la imagen de firma los pasás vos (PNG).
Leer firmas existentes — "¿ya firmó?"
// Listar todo
foreach (var f in signum.LeerFirmas(pdfBytes))
Console.WriteLine($"{f.Firmante} ({f.Cedula}) — oficial: {f.FechaOficial} — íntegra: {f.Integra}");
// ¿Ya firmó este usuario? — tolerante al formato de la cédula (todas equivalen):
signum.YaFirmo(pdf, "CPF-05-0398-0891");
signum.YaFirmo(pdf, "05-0398-0891");
signum.YaFirmo(pdf, "0503980891");
signum.YaFirmo(pdf, "503980891");
// Por nombre, estilo LIKE '%…%' (sin importar mayúsculas ni tildes):
signum.YaFirmoPorNombre(pdf, "salazar guzmán");
// Búsqueda que devuelve LAS firmas que matchean:
var mias = signum.BuscarFirmas(pdf, cedula: "503980891");
- La comparación es contra el certificado (CN y SERIALNUMBER del subject), nunca contra el texto dibujado de la estampa.
Integraverifica la criptografía (firma RSA + digest del documento). La confianza de cadena y la revocación las evalúa el validador — están fuera del alcance de esta API.- Los sellos de tiempo de documento (
EsDocTimeStamp == true) no cuentan como firmantes.
Ejemplo: bloquear el doble firmado
if (signum.YaFirmo(pdfBytes, cedulaDelUsuario))
{
ToastWarning("Ya firmaste este documento.");
return;
}
// ... abrir el flujo de firma
LTV y niveles PAdES
| Llamada | Nivel | Qué agrega | Validador CR |
|---|---|---|---|
solo Emitir() |
B-B | firma CAdES | integridad ✅, fecha ❌, validez en el tiempo ❌ |
+ ConSelloDeTiempo(tsa) |
B-T | sello de tiempo DE LA FIRMA (CAdES-T) | + fecha oficial ✅ |
+ ConLtv(tsa) |
B-LTA | DSS (cadena + OCSP/CRL embebidos) + Document Timestamp + DSS del TSA | todo verde ✅ |
ConLtv también sirve standalone sobre un PDF ya firmado:
byte[] conLtv = signum.AgregarLtv(pdfYaFirmado).ConTsa(tsaUrl).Emitir();
Comportamiento ante fallos de red (importante)
El LTV nunca pierde tu firma:
- Si la TSA no responde al agregar el Document Timestamp → se lanza
SignumLtvTimestampException, que trae el PDF con el DSS ya aplicado (PdfConDss) para que decidas usarlo. - Si falla la recolección de revocación completa → recibís la firma sin LTV (B-T), con el error disponible.
- Los fetch de OCSP/CRL corren en paralelo con timeouts cortos (OCSP 3s, CRL 10s, TSA 5s) y
caché por certificado con expiración según el
nextUpdatereal — en lotes, del segundo documento en adelante el LTV no toca la red (~90 ms).
Rendimiento
Números de referencia (medidos contra la TSA de SINPE y las CAs de CR):
| Operación | Frío | Con caché |
|---|---|---|
| Completar entero (CMS + CAdES-T + LTV) | ~320 ms | ~150 ms |
| LTV solo (DSS + DocTimeStamp + DSS del sello) | ~150 ms | ~75 ms |
| Lote de 3 documentos, completo | — | ~430 ms |
Consejo: llamá signum.PrecalentarRevocacion(cadenaDer) (fire-and-forget) apenas conozcás la
cadena del firmante — p. ej. mientras el usuario digita el PIN — y el LTV encontrará todo en caché.
Firmar varios PDFs (lote)
API de lote de primera clase: pasás un arreglo con tu id (string o guid), el PDF y la estampa opcional de cada uno — y recibís el mismo arreglo con el PDF firmado, correlacionado por id. Aislamiento por documento (uno que falla no tumba el resto) y cachés compartidos: el primer documento paga la red; del segundo en adelante el LTV tarda ~90 ms.
var docs = new[]
{
new DocumentoParaFirmar
{
Id = idSolicitud1.ToString(), // tu id o guid — vuelve tal cual
Pdf = pdf1,
Estampa = e => e.EnPagina(1).Recuadro(90, 350, 300, 58),
},
new DocumentoParaFirmar { Id = "doc-2", Pdf = pdf2 }, // sin Estampa -> firma invisible
new DocumentoParaFirmar { Id = "doc-3", Pdf = pdf3, Firmante = "NOMBRE OVERRIDE" },
};
Lote con tarjeta (diferida, 2 fases)
// FASE 1: preparar TODO el lote (prefetch de revocación automático)
var lote = signum.PrepararLote(docs, cadenaDer);
// lote.Pendientes -> [(Id, Digest)] — la tarjeta firma cada Digest
// lote.Estado -> byte[] serializable del lote completo
// (el agente firma los digests; hoy pide PIN por firma — ver nota)
// FASE 2: completar TODO, correlacionado por Id
var resultados = signum.CompletarLote(lote.Estado, firmas, tsaUrl); // firmas: [(Id, FirmaPkcs1)]
foreach (var r in resultados)
if (r.Exito) Guardar(r.Id, r.Pdf);
else Registrar(r.Id, r.Error); // este doc falló; los demás siguieron
Lote directo (sello institucional, 1 llamada)
var resultados = signum.FirmarLote(docs, certificadoConLlave, tsaUrl);
// mismo retorno: [(Id, Pdf firmado | Error)]
Sobre el PIN (diferida): la tarjeta puede firmar varios digests con una sola sesión de PIN, pero si tu agente de firma pide el PIN una vez por documento —una decisión de seguridad razonable— el lote se lo pedirá N veces. Eso se decide en el agente, no acá: Signum ya recibe las N firmas juntas en
CompletarLote.
Fuentes
No hay nada que configurar. Signum trae embebida Liberation Sans (SIL OFL 1.1), métricamente compatible con Helvetica y Arial —las tipografías de siempre en los visores de PDF—, y la registra sola la primera vez que dibuja una estampa. El resultado no depende de las fuentes que tenga el servidor, lo que importa porque un contenedor mínimo no trae ninguna.
Si tu aplicación ya registró un IFontResolver de PDFsharp (por ejemplo para usar tipografías
corporativas), Signum respeta el tuyo y no lo sobrescribe.
Alcance y límites
- Entrada: PDFs sin cifrar (PDF 1.4–2.0, xref clásico o comprimido). PDFs cifrados: no soportados.
- Multi-firma: se puede firmar un documento que ya está firmado — pueden firmar todas las
personas que haga falta. La firma nueva se agrega como revisión incremental (los bytes ya
firmados quedan intactos), y el LTV re-emite el
/DSScon el material de TODAS las firmas. - El digest es SHA-256 y la firma RSA PKCS#1 v1.5 (lo que producen las tarjetas SINPE). Otros algoritmos: fuera del alcance inicial.
- Verificación (
LeerFirmas): integridad criptográfica, no evaluación de confianza/revocación.
Conformidad: qué estándar cumple, y en qué países sirve
PAdES no es un formato costarricense. Es un estándar de ETSI, el organismo europeo de
normalización, publicado como EN 319 142. Costa Rica no definió un formato propio: lo adoptó.
La Política de Formatos Oficiales de los Documentos Electrónicos Firmados
Digitalmente
(MICITT/DCFD, publicada en La Gaceta N.º 95 del 20 de mayo de 2013; vigente la v4 de junio de 2025)
establece PAdES para los PDF, exige LTV —validación a largo plazo— y recomienda el perfil
Baseline. Eso es exactamente lo que produce ConSelloDeTiempo + ConLtv: PAdES Baseline
B-LTA, el nivel más alto del perfil.
Como el estándar es internacional, el mismo PDF que Signum firma para Costa Rica vale en cualquier país que reconozca PAdES —toda la Unión Europea por eIDAS, y buena parte de Latinoamérica—. Lo que cambia de un país a otro es configuración, no código:
| Qué cambia | Cómo se resuelve |
|---|---|
| Autoridad de sellado de tiempo | Parámetro: ConSelloDeTiempo("https://tsa-de-tu-pais/…"). Sirve cualquier TSA RFC 3161 |
| Cadena de certificados | La aporta la tarjeta o el PFX, sea del país que sea |
| A quién pedirle OCSP/CRL | Automático: sale de las extensiones AIA y CRL Distribution Points del propio certificado |
| Cómo se ve la estampa | Configurable, y sin ningún logo embebido |
Dos límites reales, que conviene conocer antes de adoptarla fuera de Costa Rica:
- Solo RSA PKCS#1 v1.5 sobre SHA-256, que es lo que emiten las tarjetas costarricenses. Una PKI que firme con ECDSA o RSA-PSS todavía no está soportada.
YaFirmoasume una identificación numérica. Normaliza a dígitos para tolerar los formatos de la cédula de Costa Rica (CPF-05-0398-0891≡503980891), así que con un documento alfanumérico —un NIE español, por ejemplo— descarta las letras.YaFirmoPorNombreyBuscarFirmas(nombre:)no tienen esa limitación.
Todo lo demás —el CMS, el sello de tiempo, el DSS, la revisión incremental— es agnóstico del país.
Estándares implementados
ISO 32000-2 (PDF 2.0, §12.8 firmas, §7.5.6 incremental updates) · ETSI EN 319 142-1 (PAdES baseline B/T/LT/LTA) · ETSI TS 102 778-4 (LTV: DSS y Document Timestamp) · RFC 5652 (CMS) · RFC 5035 (ESS signingCertificateV2) · RFC 3161 (sello de tiempo) · RFC 6960 (OCSP).
Cada clase cita en su documentación la sección de la especificación que implementa. Signum es una implementación limpia desde los estándares públicos; no deriva de ninguna librería con licencia restrictiva.
Licencia
MIT.
| 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 | net461 was computed. 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. |
-
.NETStandard 2.0
- BouncyCastle.Cryptography (>= 2.6.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- PDFsharp (>= 6.2.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Ver CHANGELOG.md