From 25b72a02231ec2bc9a93024ca7eaecd244f53cfd Mon Sep 17 00:00:00 2001 From: manuel Date: Wed, 2 Sep 2026 16:02:35 +0200 Subject: [PATCH] 3.0.7.0 2026-05-20 Se elimina PdfSignatureTools, Nuevo metodo ObtenerPropiedadDePdf --- Depurador TSpdfUtils/Form1.vb | 40 +- TSPdfUtils/TSpdfUtils.vbproj | 9 +- TSPdfUtils/VerificadorPDFFirmado.vb | 764 ++++++++++++++++++++++++++++ TSPdfUtils/pdf.vb | 44 ++ 4 files changed, 853 insertions(+), 4 deletions(-) create mode 100644 TSPdfUtils/VerificadorPDFFirmado.vb 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)