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