Files
TSpdfUtils/TSPdfUtils/VerificadorPDFFirmado.vb

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