diff --git a/Depurador TSpdfUtils/Form1.vb b/Depurador TSpdfUtils/Form1.vb
index 4cdf29c..6a06137 100644
--- a/Depurador TSpdfUtils/Form1.vb
+++ b/Depurador TSpdfUtils/Form1.vb
@@ -1,10 +1,48 @@
Imports System.IO
Imports System.Security.Cryptography
Imports System.Security.Cryptography.X509Certificates
+Imports TSpdfUtils
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
- FirmaPDFPFX("prueba", "prueba", "prueba", "c:\tmp\pruebafirmada.pdf", True, True)
+ 'FirmaPDFPFX("prueba", "prueba", "prueba", "c:\tmp\pruebafirmada.pdf", True, True)
+
+ 'Dim ms = New MemoryStream(System.IO.File.ReadAllBytes("c:\tmp\pruebasolicitudcertificado.pdf"))
+ 'TSpdfUtils.PdfSignatureTools.MostrarRevisiones(ms)
+
+ Dim resultado As VerificadorPDFFirmado.Resultado
+
+ Using pdfOriginal As FileStream =
+ File.OpenRead("C:\tmp\o1.pdf")
+
+ Using pdfFirmado As FileStream =
+ File.OpenRead("C:\tmp\f1.pdf")
+
+ resultado =
+ VerificadorPDFFirmado.Verificar(
+ PdfOriginal:=pdfOriginal,
+ PdfFirmado:=pdfFirmado,
+ NombreCampoFirma1:="FirmaCliente",
+ NombreCampoFirma2:="",
+ ComprobarSegundaFirma:=False
+ )
+
+ End Using
+ End Using
+
+ If resultado.DocumentoValido Then
+ Console.WriteLine("PDF CORRECTO")
+ Else
+ Console.WriteLine(resultado.Mensaje)
+ End If
+
+
+
+
+
+
+
+
End Sub
Public Shared Sub FirmaPDFPFX(Razon As String, Contacto As String, Localizacion As String, FicheroPdfDef As String, EliminaFirmasAnteriores As Boolean, Optional EliminaFicheroDestino As Boolean = False)
diff --git a/TSPdfUtils/TSpdfUtils.vbproj b/TSPdfUtils/TSpdfUtils.vbproj
index a64569e..20d046c 100644
--- a/TSPdfUtils/TSpdfUtils.vbproj
+++ b/TSPdfUtils/TSpdfUtils.vbproj
@@ -19,13 +19,16 @@
TSpdfUtils
TSpdfUtils
libreria, netstandard2.0
- 3.0.4.0
- 3.0.4.0
- 3.0.4.0
+ 3.0.7.0
+ 3.0.7.0
+ 3.0.7.0
Manuel
Tecnosis S.A
Utilidades de tratamiento de pdf, de firmas digitales e imagenes.
+ - 3.0.7.0 2026-05-20 Se elimina PdfSignatureTools, Nuevo metodo ObtenerPropiedadDePdf
+ - 3.0.6.0 2026-05-20 Correccion PdfSignatureTools
+ - 3.0.5.0 2026-05-20 Nuevas rutinas en PdfSignatureTools
- 3.0.4.0 2026-05-20 Actualización tsPDF
- 3.0.0.0 2026-05-20 Correcciones dependencias Microsoft.Extensions.Logging
- 3.0.0.0 2025-10-23 Actualización de tspdfutilscore
diff --git a/TSPdfUtils/VerificadorPDFFirmado.vb b/TSPdfUtils/VerificadorPDFFirmado.vb
new file mode 100644
index 0000000..c2fa061
--- /dev/null
+++ b/TSPdfUtils/VerificadorPDFFirmado.vb
@@ -0,0 +1,764 @@
+Option Strict On
+Option Explicit On
+
+Imports System
+Imports System.Collections.Generic
+Imports System.IO
+Imports System.Security.Cryptography
+Imports iText.Kernel.Pdf
+Imports iText.Signatures
+Imports TSpdf.Kernel.Pdf
+Imports TSpdf.Signatures
+
+
+Public NotInheritable Class VerificadorPDFFirmado
+
+ Private Sub New()
+ End Sub
+
+
+ ' ================================================================
+ ' RESULTADO
+ ' ================================================================
+
+ Public Class Resultado
+
+ ' ------------------------------------------------------------
+ ' Documento original
+ ' ------------------------------------------------------------
+
+ Public Property DocumentoOriginalValido As Boolean
+
+ Public Property SHA256Original As String
+
+
+ ' ------------------------------------------------------------
+ ' Firma 1
+ ' ------------------------------------------------------------
+
+ Public Property Firma1Existe As Boolean
+
+ Public Property Firma1Valida As Boolean
+
+ Public Property Firma1CubreDocumento As Boolean
+
+ Public Property FechaFirma1 As DateTime?
+
+
+ ' ------------------------------------------------------------
+ ' Firma 2
+ ' ------------------------------------------------------------
+
+ Public Property Firma2Existe As Boolean
+
+ Public Property Firma2Valida As Boolean
+
+ Public Property Firma2CubreDocumento As Boolean
+
+ Public Property FechaFirma2 As DateTime?
+
+
+ ' ------------------------------------------------------------
+ ' Revisiones
+ ' ------------------------------------------------------------
+
+ Public Property RevisionFirma1 As Integer
+
+ Public Property RevisionFirma2 As Integer
+
+ Public Property TotalRevisiones As Integer
+
+ Public Property NoHayCambiosPosteriores As Boolean
+
+
+ ' ------------------------------------------------------------
+ ' Control
+ ' ------------------------------------------------------------
+
+ Public Property ComprobarSegundaFirma As Boolean
+
+ Public Property Mensaje As String
+
+
+ ' ------------------------------------------------------------
+ ' RESULTADO GLOBAL
+ ' ------------------------------------------------------------
+
+ Public ReadOnly Property DocumentoValido As Boolean
+
+ Get
+
+ ' La primera firma y el documento original
+ ' siempre son obligatorios.
+
+ Dim valido As Boolean =
+ DocumentoOriginalValido AndAlso
+ Firma1Existe AndAlso
+ Firma1Valida
+
+
+ ' Si se solicita comprobar la segunda firma,
+ ' también debe existir y ser válida.
+
+ If ComprobarSegundaFirma Then
+
+ valido =
+ valido AndAlso
+ Firma2Existe AndAlso
+ Firma2Valida AndAlso
+ NoHayCambiosPosteriores
+
+ End If
+
+
+ Return valido
+
+ End Get
+
+ End Property
+
+ End Class
+
+
+ ' ================================================================
+ ' FUNCIÓN PRINCIPAL
+ ' ================================================================
+
+ Public Shared Function Verificar(
+ PdfOriginal As Stream,
+ PdfFirmado As Stream,
+ NombreCampoFirma1 As String,
+ NombreCampoFirma2 As String,
+ Optional ComprobarSegundaFirma As Boolean = True
+ ) As Resultado
+
+
+ Dim resultado As New Resultado()
+
+ resultado.ComprobarSegundaFirma =
+ ComprobarSegundaFirma
+
+
+ Try
+
+ ' ========================================================
+ ' VALIDAR PARÁMETROS
+ ' ========================================================
+
+ If PdfOriginal Is Nothing Then
+
+ Throw New ArgumentNullException(
+ NameOf(PdfOriginal)
+ )
+
+ End If
+
+
+ If PdfFirmado Is Nothing Then
+
+ Throw New ArgumentNullException(
+ NameOf(PdfFirmado)
+ )
+
+ End If
+
+
+ If Not PdfOriginal.CanSeek Then
+
+ Throw New ArgumentException(
+ "PdfOriginal debe permitir Seek.",
+ NameOf(PdfOriginal)
+ )
+
+ End If
+
+
+ If Not PdfFirmado.CanSeek Then
+
+ Throw New ArgumentException(
+ "PdfFirmado debe permitir Seek.",
+ NameOf(PdfFirmado)
+ )
+
+ End If
+
+
+ If String.IsNullOrWhiteSpace(
+ NombreCampoFirma1
+ ) Then
+
+ Throw New ArgumentException(
+ "Debe indicar el nombre del primer campo de firma.",
+ NameOf(NombreCampoFirma1)
+ )
+
+ End If
+
+
+ If ComprobarSegundaFirma AndAlso
+ String.IsNullOrWhiteSpace(NombreCampoFirma2) Then
+
+ Throw New ArgumentException(
+ "Debe indicar el nombre del segundo campo de firma.",
+ NameOf(NombreCampoFirma2)
+ )
+
+ End If
+
+
+ ' ========================================================
+ ' 1. SHA256 DEL PDF ORIGINAL
+ ' ========================================================
+
+ resultado.SHA256Original =
+ CalcularSHA256(PdfOriginal)
+
+
+ ' ========================================================
+ ' 2. COMPROBAR QUE EL ORIGINAL COINCIDE CON EL PDF
+ ' FIRMADO
+ '
+ ' En una firma incremental, la revisión original se
+ ' conserva al principio del PDF.
+ ' ========================================================
+
+ resultado.DocumentoOriginalValido =
+ ComprobarOriginalPorPrefijo(
+ PdfOriginal,
+ PdfFirmado
+ )
+
+
+ If Not resultado.DocumentoOriginalValido Then
+
+ resultado.Mensaje =
+ "El PDF firmado no comienza con el mismo " &
+ "contenido que el PDF original."
+
+ Return resultado
+
+ End If
+
+
+ ' ========================================================
+ ' 3. ABRIR PDF FIRMADO
+ ' ========================================================
+
+ PdfFirmado.Position = 0
+
+
+ Using reader As New PdfReader(PdfFirmado)
+
+ reader.SetUnethicalReading(True)
+
+
+ Using pdfDoc As New PdfDocument(reader)
+
+ Dim signUtil As New SignatureUtil(
+ pdfDoc
+ )
+
+
+ ' =================================================
+ ' 4. OBTENER FIRMAS
+ ' =================================================
+
+ Dim nombresFirmas As IList(Of String) =
+ signUtil.GetSignatureNames()
+
+
+ ' =================================================
+ ' 5. FIRMA 1
+ ' =================================================
+
+ resultado.Firma1Existe =
+ nombresFirmas.Contains(
+ NombreCampoFirma1
+ )
+
+
+ If Not resultado.Firma1Existe Then
+
+ resultado.Mensaje =
+ "No se ha encontrado la primera firma: '" &
+ NombreCampoFirma1 &
+ "'."
+
+ Return resultado
+
+ End If
+
+
+ ' =================================================
+ ' 6. VALIDAR FIRMA 1
+ ' =================================================
+
+ resultado.Firma1Valida =
+ ComprobarFirma(
+ signUtil,
+ NombreCampoFirma1
+ )
+
+
+ ' =================================================
+ ' 7. COMPROBAR SI FIRMA 1 CUBRE EL DOCUMENTO
+ ' =================================================
+
+ resultado.Firma1CubreDocumento =
+ signUtil.SignatureCoversWholeDocument(
+ NombreCampoFirma1
+ )
+
+
+ ' =================================================
+ ' 8. INFORMACIÓN FIRMA 1
+ ' =================================================
+
+ resultado.FechaFirma1 =
+ ObtenerFechaFirma(
+ signUtil,
+ NombreCampoFirma1
+ )
+
+
+ ' =================================================
+ ' 9. REVISIÓN FIRMA 1
+ ' =================================================
+
+ resultado.RevisionFirma1 =
+ signUtil.GetRevision(
+ NombreCampoFirma1
+ )
+
+
+ ' =================================================
+ ' 10. SI NO SE COMPRUEBA FIRMA 2
+ ' =================================================
+
+ If Not ComprobarSegundaFirma Then
+
+ resultado.Firma2Existe = False
+ resultado.Firma2Valida = True
+ resultado.Firma2CubreDocumento = True
+
+ resultado.NoHayCambiosPosteriores = True
+
+
+ If resultado.DocumentoOriginalValido AndAlso
+ resultado.Firma1Valida Then
+
+ resultado.Mensaje =
+ "PDF válido. El documento original " &
+ "coincide y la primera firma es válida. " &
+ "La segunda firma no se ha comprobado."
+
+ Else
+
+ resultado.Mensaje =
+ "El PDF no ha superado las " &
+ "comprobaciones de la primera firma."
+
+ End If
+
+
+ Return resultado
+
+ End If
+
+
+ ' =================================================
+ ' 11. FIRMA 2
+ ' =================================================
+
+ resultado.Firma2Existe =
+ nombresFirmas.Contains(
+ NombreCampoFirma2
+ )
+
+
+ If Not resultado.Firma2Existe Then
+
+ resultado.Mensaje =
+ "No se ha encontrado la segunda firma: '" &
+ NombreCampoFirma2 &
+ "'."
+
+ Return resultado
+
+ End If
+
+
+ ' =================================================
+ ' 12. VALIDAR FIRMA 2
+ ' =================================================
+
+ resultado.Firma2Valida =
+ ComprobarFirma(
+ signUtil,
+ NombreCampoFirma2
+ )
+
+
+ ' =================================================
+ ' 13. COMPROBAR SI FIRMA 2 CUBRE EL DOCUMENTO
+ ' =================================================
+
+ resultado.Firma2CubreDocumento =
+ signUtil.SignatureCoversWholeDocument(
+ NombreCampoFirma2
+ )
+
+
+ ' =================================================
+ ' 14. INFORMACIÓN FIRMA 2
+ ' =================================================
+
+ resultado.FechaFirma2 =
+ ObtenerFechaFirma(
+ signUtil,
+ NombreCampoFirma2
+ )
+
+
+ ' =================================================
+ ' 15. REVISIÓN FIRMA 2
+ ' =================================================
+
+ resultado.RevisionFirma2 =
+ signUtil.GetRevision(
+ NombreCampoFirma2
+ )
+
+
+ ' =================================================
+ ' 16. TOTAL DE REVISIONES
+ ' =================================================
+
+ resultado.TotalRevisiones =
+ signUtil.GetTotalRevisions()
+
+
+ ' =================================================
+ ' 17. ORDEN DE LAS FIRMAS
+ ' =================================================
+
+ If resultado.RevisionFirma1 >=
+ resultado.RevisionFirma2 Then
+
+ resultado.Mensaje =
+ "La segunda firma no es posterior " &
+ "a la primera."
+
+ Return resultado
+
+ End If
+
+
+ ' =================================================
+ ' 18. COMPROBAR QUE FIRMA 2 ES LA ÚLTIMA
+ ' =================================================
+
+ resultado.NoHayCambiosPosteriores =
+ (
+ resultado.RevisionFirma2 =
+ resultado.TotalRevisiones
+ )
+
+ End Using
+
+ End Using
+
+
+ ' ========================================================
+ ' 19. RESULTADO FINAL
+ ' ========================================================
+
+ If resultado.DocumentoValido Then
+
+ resultado.Mensaje =
+ "PDF válido. El documento original coincide, " &
+ "las firmas son válidas y no existen cambios " &
+ "posteriores."
+
+ Else
+
+ resultado.Mensaje =
+ "El PDF NO ha superado todas las comprobaciones."
+
+ End If
+
+
+ Return resultado
+
+
+ Catch ex As Exception
+
+ Throw New Exception(
+ "Error verificando el PDF firmado: " &
+ ex.Message,
+ ex
+ )
+
+ End Try
+
+ End Function
+
+
+ ' ================================================================
+ ' COMPROBAR FIRMA
+ ' ================================================================
+
+ Private Shared Function ComprobarFirma(
+ signUtil As SignatureUtil,
+ NombreFirma As String
+ ) As Boolean
+
+ Try
+
+ Dim pkcs7 As PdfPKCS7 =
+ signUtil.ReadSignatureData(
+ NombreFirma
+ )
+
+
+ If pkcs7 Is Nothing Then
+ Return False
+ End If
+
+
+ ' En las versiones de iText 7 que utilizan tu API,
+ ' la comprobación se realiza desde PdfPKCS7.
+
+ Return pkcs7.VerifySignatureIntegrityAndAuthenticity()
+
+
+ Catch
+
+ Return False
+
+ End Try
+
+ End Function
+
+
+ ' ================================================================
+ ' OBTENER FECHA DE FIRMA
+ ' ================================================================
+
+ Private Shared Function ObtenerFechaFirma(
+ signUtil As SignatureUtil,
+ NombreFirma As String
+ ) As DateTime?
+
+ Try
+
+ Dim pkcs7 As PdfPKCS7 =
+ signUtil.ReadSignatureData(
+ NombreFirma
+ )
+
+
+ If pkcs7 Is Nothing Then
+ Return Nothing
+ End If
+
+
+ Return pkcs7.GetSignDate()
+
+
+ Catch
+
+ Return Nothing
+
+ End Try
+
+ End Function
+
+
+ ' ================================================================
+ ' CALCULAR SHA256
+ ' ================================================================
+
+ Public Shared Function CalcularSHA256(
+ Pdf As Stream
+ ) As String
+
+ If Pdf Is Nothing Then
+
+ Throw New ArgumentNullException(
+ NameOf(Pdf)
+ )
+
+ End If
+
+
+ If Not Pdf.CanSeek Then
+
+ Throw New ArgumentException(
+ "El Stream debe permitir Seek.",
+ NameOf(Pdf)
+ )
+
+ End If
+
+
+ Dim posicionOriginal As Long =
+ Pdf.Position
+
+
+ Try
+
+ Pdf.Position = 0
+
+
+ Using sha As SHA256 =
+ SHA256.Create()
+
+ Dim hash As Byte() =
+ sha.ComputeHash(Pdf)
+
+
+ Return BitConverter.ToString(hash).
+ Replace("-", "").
+ ToLowerInvariant()
+
+ End Using
+
+
+ Finally
+
+ Pdf.Position =
+ posicionOriginal
+
+ End Try
+
+ End Function
+
+
+ ' ================================================================
+ ' COMPROBAR QUE EL PDF FIRMADO CONTIENE EL PDF ORIGINAL
+ ' ================================================================
+
+ Private Shared Function ComprobarOriginalPorPrefijo(
+ PdfOriginal As Stream,
+ PdfFirmado As Stream
+ ) As Boolean
+
+ If PdfOriginal Is Nothing OrElse
+ PdfFirmado Is Nothing Then
+
+ Return False
+
+ End If
+
+
+ If Not PdfOriginal.CanSeek OrElse
+ Not PdfFirmado.CanSeek Then
+
+ Throw New ArgumentException(
+ "Los streams deben permitir Seek."
+ )
+
+ End If
+
+
+ Dim posicionOriginal As Long =
+ PdfOriginal.Position
+
+ Dim posicionFirmado As Long =
+ PdfFirmado.Position
+
+
+ Try
+
+ PdfOriginal.Position = 0
+ PdfFirmado.Position = 0
+
+
+ ' El PDF firmado tiene que ser al menos
+ ' tan grande como el original.
+
+ If PdfFirmado.Length <
+ PdfOriginal.Length Then
+
+ Return False
+
+ End If
+
+
+ Dim bufferOriginal(8191) As Byte
+ Dim bufferFirmado(8191) As Byte
+
+
+ Dim restantes As Long =
+ PdfOriginal.Length
+
+
+ While restantes > 0
+
+ Dim cantidad As Integer =
+ CInt(
+ Math.Min(
+ restantes,
+ bufferOriginal.Length
+ )
+ )
+
+
+ Dim leidosOriginal As Integer =
+ PdfOriginal.Read(
+ bufferOriginal,
+ 0,
+ cantidad
+ )
+
+
+ Dim leidosFirmado As Integer =
+ PdfFirmado.Read(
+ bufferFirmado,
+ 0,
+ cantidad
+ )
+
+
+ If leidosOriginal <> cantidad OrElse
+ leidosFirmado <> cantidad Then
+
+ Return False
+
+ End If
+
+
+ For i As Integer = 0 To cantidad - 1
+
+ If bufferOriginal(i) <>
+ bufferFirmado(i) Then
+
+ Return False
+
+ End If
+
+ Next
+
+
+ restantes -= cantidad
+
+ End While
+
+
+ Return True
+
+
+ Finally
+
+ PdfOriginal.Position =
+ posicionOriginal
+
+ PdfFirmado.Position =
+ posicionFirmado
+
+ End Try
+
+ End Function
+
+End Class
\ No newline at end of file
diff --git a/TSPdfUtils/pdf.vb b/TSPdfUtils/pdf.vb
index ec140b7..2c46b62 100644
--- a/TSPdfUtils/pdf.vb
+++ b/TSPdfUtils/pdf.vb
@@ -691,7 +691,51 @@ Public Class pdf
Throw New Exception(ex.Message, ex)
End Try
End Function
+ Public Shared Function ObtenerPropiedadDePdf(
+ FicheroPdf As MemoryStream,
+ NombrePropiedad As String) As String
+ Try
+ If FicheroPdf Is Nothing Then
+ Throw New ArgumentNullException(NameOf(FicheroPdf))
+ End If
+
+ If FicheroPdf.CanSeek Then
+ FicheroPdf.Position = 0
+ End If
+
+ Using reader As New PdfReader(FicheroPdf)
+ Using pdfDocument As New PdfDocument(reader)
+
+ Dim info As PdfDictionary =
+ pdfDocument.GetTrailer().GetAsDictionary(PdfName.Info)
+
+ If info Is Nothing Then
+ Return Nothing
+ End If
+
+ Dim valor As PdfString =
+ info.GetAsString(New PdfName(NombrePropiedad))
+
+ If valor Is Nothing Then
+ Return Nothing
+ End If
+
+ Return valor.ToUnicodeString()
+
+ End Using
+ End Using
+
+ Catch ex As Exception
+ Throw New Exception(
+ "Error obteniendo la propiedad '" &
+ NombrePropiedad &
+ "': " &
+ ex.Message,
+ ex)
+ End Try
+
+ End Function
Public Shared Function ExtractTextFromPDF(ByVal RutaPdf As String) As String
Dim pdfReader As PdfReader = New PdfReader(RutaPdf)
Dim pdfDoc As PdfDocument = New PdfDocument(pdfReader)