764 lines
20 KiB
VB.net
764 lines
20 KiB
VB.net
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 |