commit 905514c2a995ac5534a2e70b284dca5f91d7bf5b Author: manuel Date: Wed May 27 17:48:50 2026 +0200 Agregar archivos de proyecto. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/TSPdfUtils.sln b/TSPdfUtils.sln new file mode 100644 index 0000000..9acb2a4 --- /dev/null +++ b/TSPdfUtils.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36301.6 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "TSpdfUtils", "TSPdfUtils\TSpdfUtils.vbproj", "{EBE96C43-7754-94F0-D5A0-52CC0CC95392}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tsPDFUtilsCore", "tsPDFUtilsCore\tsPDFUtilsCore.csproj", "{87114C69-A12B-FADE-2C87-D199628C9366}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EBE96C43-7754-94F0-D5A0-52CC0CC95392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBE96C43-7754-94F0-D5A0-52CC0CC95392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBE96C43-7754-94F0-D5A0-52CC0CC95392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBE96C43-7754-94F0-D5A0-52CC0CC95392}.Release|Any CPU.Build.0 = Release|Any CPU + {87114C69-A12B-FADE-2C87-D199628C9366}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87114C69-A12B-FADE-2C87-D199628C9366}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87114C69-A12B-FADE-2C87-D199628C9366}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87114C69-A12B-FADE-2C87-D199628C9366}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6C498ECD-A179-4303-8D50-CE1FF890325C} + EndGlobalSection +EndGlobal diff --git a/TSPdfUtils/ExternalTimestampContainer.vb b/TSPdfUtils/ExternalTimestampContainer.vb new file mode 100644 index 0000000..c32f128 --- /dev/null +++ b/TSPdfUtils/ExternalTimestampContainer.vb @@ -0,0 +1,64 @@ + +Imports System.IO +Imports TSpdf.Kernel.Font + +Imports System.Security.Cryptography.X509Certificates + +Imports System.Security.Cryptography +Imports TSpdf.Kernel.Pdf +Imports TSpdf.Kernel.Utils +Imports TSpdf.Forms +Imports TSpdf.Signatures +Imports System.Text +Imports TSpdf.Kernel.Pdf.Canvas +Imports TSpdf.Kernel.Geom +Imports TSpdf.Layout.Element +Imports System.Text.RegularExpressions +Imports Org.BouncyCastle.Pkcs +Imports TSpdf.Commons.Bouncycastle.Cert +Imports TSpdf.Commons.Bouncycastle.Crypto +Imports TSpdf.Bouncycastle.Crypto +Imports TSpdf.Bouncycastle.Cert +Imports TSpdf +Imports Org.BouncyCastle.Crypto.Agreement +Imports TSpdf.Layout +Imports TSpdf.IO.Font.Constants +Imports TSpdf.Layout.Properties +Imports System.Drawing +Imports System.Drawing.Imaging +Imports Rectangle = System.Drawing.Rectangle +Imports TSpdf.Kernel.Pdf.Colorspace.PdfDeviceCs +Imports TSpdf.Layout.Renderer +Imports System.Drawing.Text +Imports System.Runtime.InteropServices +Imports TSpdf.Forms.Fields +Imports System.Runtime.CompilerServices +Imports TSpdf.Signatures.PdfSigner +Imports tsPDFUtilsCore + +Public Class ExternalTimestampContainer + Implements IExternalSignatureContainer + + Private ReadOnly tsaClient As ITSAClient + + Public Sub New(ByVal tsaClient As ITSAClient) + Me.tsaClient = tsaClient + End Sub + + Public Function Sign(ByVal data As Stream) As Byte() Implements IExternalSignatureContainer.Sign + ' Calcula el hash del contenido usando SHA256 + Dim hash As Byte() + Using hasher As HashAlgorithm = SHA256.Create() + hash = hasher.ComputeHash(data) + End Using + + ' Solicita la marca de tiempo a la TSA + Return tsaClient.GetTimeStampToken(hash) + End Function + + Public Sub ModifySigningDictionary(ByVal signDic As PdfDictionary) Implements IExternalSignatureContainer.ModifySigningDictionary + signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite) + signDic.Put(PdfName.SubFilter, PdfName.ETSI_RFC3161) + End Sub + +End Class diff --git a/TSPdfUtils/FirmaTiempo.vb b/TSPdfUtils/FirmaTiempo.vb new file mode 100644 index 0000000..7af5055 --- /dev/null +++ b/TSPdfUtils/FirmaTiempo.vb @@ -0,0 +1,52 @@ +Imports System.IO +Imports Org.BouncyCastle.Pkcs +Imports TSpdf.Bouncycastle.Cert +Imports TSpdf.Bouncycastle.Crypto +Imports TSpdf.Commons.Bouncycastle.Cert +Imports TSpdf.Commons.Bouncycastle.Crypto +Imports TSpdf.Kernel.Pdf +Imports TSpdf.Signatures + +Public Class FirmaTiempo + + Public Shared Sub SignPdfWithTimestamp(src As String, dest As String, keystorePath As Stream, keystorePassword As String, tsaUrl As String) + Dim reader As New PdfReader(src) + Dim output As New FileStream(dest, FileMode.Create) + Dim signer As New PdfSigner(reader, output, New StampingProperties()) + + + ' Se coge los datos del certificado + Dim cpassword = keystorePassword.ToCharArray + + Dim [alias] As String = Nothing + Dim pk12 As Pkcs12Store = New Pkcs12Store(keystorePath, cpassword) + + For Each a In pk12.Aliases + [alias] = (CType(a, String)) + + If pk12.IsKeyEntry([alias]) Then + Exit For + End If + Next + + Dim pk As IPrivateKey = New PrivateKeyBC(pk12.GetKey([alias]).Key) + Dim pks = New PrivateKeySignature(pk, DigestAlgorithms.SHA256) + + + Dim ce As X509CertificateEntry() = pk12.GetCertificateChain([alias]) + Dim chain = New IX509Certificate(ce.Length - 1) {} + + For k As Integer = 0 To ce.Length - 1 + chain(k) = New X509CertificateBC(ce(k).Certificate) + Next + + Dim externalSignature As IExternalSignature = New PrivateKeySignature(pk, "SHA-256") + + ' Aqui va la URL del servicio de sellado de tiempo + Dim tsaClient As ITSAClient = New TSAClientBouncyCastle(tsaUrl) + + ' Se hace la firma con el sellado de tiempo que se le pasa por parámetro + signer.SignDetached(externalSignature, chain, Nothing, Nothing, tsaClient, 0, PdfSigner.CryptoStandard.CADES) + End Sub + +End Class diff --git a/TSPdfUtils/Firmas.vb b/TSPdfUtils/Firmas.vb new file mode 100644 index 0000000..5318523 --- /dev/null +++ b/TSPdfUtils/Firmas.vb @@ -0,0 +1,1259 @@ + +Imports System.Drawing +Imports System.Drawing.Imaging +Imports System.Drawing.Text +Imports System.IO +Imports System.Runtime.CompilerServices +Imports System.Runtime.InteropServices +Imports System.Security.Cryptography +Imports System.Security.Cryptography.X509Certificates +Imports System.Text +Imports System.Text.RegularExpressions +Imports Org.BouncyCastle.Crypto.Agreement +Imports Org.BouncyCastle.Pkcs +Imports TSpdf +Imports TSpdf.Bouncycastle.Cert +Imports TSpdf.Bouncycastle.Crypto +Imports TSpdf.Commons.Bouncycastle.Cert +Imports TSpdf.Commons.Bouncycastle.Crypto +Imports TSpdf.Forms +Imports TSpdf.Forms.Fields +Imports TSpdf.IO.Font +Imports TSpdf.IO.Font.Constants +Imports TSpdf.Kernel.Font +Imports TSpdf.Kernel.Geom +Imports TSpdf.Kernel.Pdf +Imports TSpdf.Kernel.Pdf.Canvas +Imports TSpdf.Kernel.Pdf.Colorspace.PdfDeviceCs +Imports TSpdf.Kernel.Utils +Imports TSpdf.Layout +Imports TSpdf.Layout.Element +Imports TSpdf.Layout.Properties +Imports TSpdf.Layout.Renderer +Imports TSpdf.Pdfa +Imports TSpdf.Signatures +Imports TSpdf.Signatures.PdfSigner +Imports tsPDFUtilsCore +Imports Rectangle = System.Drawing.Rectangle + +Public Class Firmas + + + Public Shared Sub AddTimeStamp(ByVal inputPdf As String, ByVal outputPdf As String) + Dim reader As New PdfReader(inputPdf) + Dim writer As New PdfWriter(outputPdf) + + Dim signer As New PdfSigner(reader, writer, New StampingProperties()) + + ' Configura el cliente TSA + Dim tsaUrl As String = "http://timestamp.digicert.com" ' Puedes usar otra TSA pública o privada + Dim tsaClient As New TSAClientBouncyCastle(tsaUrl, Nothing, Nothing, 4096, "SHA-256") + + ' Crear una firma externa basada solo en la marca de tiempo + Dim external As IExternalSignatureContainer = New ExternalTimestampContainer(tsaClient) + + ' Aplicar la marca de tiempo + signer.SignExternalContainer(external, 8192) + End Sub + + Public Shared Function ObtieneFirmantesPDFFirmado(FicheroPdf As String, SoloCertId As Boolean) As List(Of String) + Try + Dim pdfReader As New PdfReader(FicheroPdf) + Dim pdfDoc As New PdfDocument(pdfReader) + Dim su As New SignatureUtil(pdfDoc) + Dim firmantes As New List(Of String) + For Each n In su.GetSignatureNames + Dim sd = su.ReadSignatureData(n) + If sd.VerifySignatureIntegrityAndAuthenticity Then + Dim cert = sd.GetSigningCertificate + Dim b = cert.GetEncoded + Dim scertid = SHA1(b) + If SoloCertId Then + firmantes.Add(scertid) + Else + Dim sTitulo As String = "" + Dim sNombreCompleto As String = "" + Dim sNumeroColegiado As String = "" + Dim sDI As String = "" + Try + sTitulo = UtilsCert.ObtenerValorAtributoDN(b, "2.5.4.3") + DescomponerCNsuscriptor(sTitulo, sNombreCompleto, sDI, sNumeroColegiado) + Catch + Try + Catch + sNombreCompleto = UtilsCert.ObtenerValorAtributoDN(b, "2.5.4.4") & ", " & UtilsCert.ObtenerValorAtributoDN(b, "2.5.4.42") + sDI = UtilsCert.ObtenerValorAtributoDN(b, "2.5.4.5") + If sDI.StartsWith("IDCES-") Then sDI = sDI.Split("-")(1) + End Try + End Try + firmantes.Add(scertid & "|" & sNombreCompleto & " (" & sDI & ")") + End If + End If + Next + Return firmantes + Catch ex As Exception + Throw ex + End Try + End Function + Public Shared Function DescomponerCNsuscriptor(ByVal CNsuscriptor As String, ByRef Optional nombre As String = Nothing, ByRef Optional docIdentidad As String = Nothing, ByRef Optional numPersonal As String = Nothing) As Boolean + nombre = CNsuscriptor + docIdentidad = Nothing + numPersonal = Nothing + + If CNsuscriptor = "" Then + Return False + End If + + Dim text As String = Nothing + Dim text2 As String = Nothing + Dim array As String() = Regex.Split(CNsuscriptor, " DI=") + + If array.Length > 2 Then + Return False + End If + + Dim text3 As String + + If array.Length = 2 Then + text3 = array(0) + array = Regex.Split(array(1), " N=") + + If array.Length > 2 Then + Return False + End If + + text = array(0) + + If array.Length = 2 Then + text2 = array(1) + End If + Else + array = Regex.Split(CNsuscriptor, " N=") + + If array.Length > 2 Then + Return False + End If + + text3 = array(0) + + If array.Length = 2 Then + text2 = array(1) + End If + End If + + If text3 = "" OrElse text3.Trim = "" OrElse text3.IndexOf("=") >= 0 Then + Return False + End If + + If text IsNot Nothing AndAlso text.Trim() = "" OrElse text.IndexOf("=") >= 0 Then + Return False + End If + + If text2 IsNot Nothing AndAlso (text2.Trim() = "" OrElse text2.IndexOf("=") >= 0) Then + Return False + End If + + nombre = text3 + docIdentidad = text + numPersonal = text2 + Return True + End Function + Public Shared Function SHA1(ByVal Datos() As Byte) As String + Dim sha1Obj As New Security.Cryptography.SHA1CryptoServiceProvider + Dim bytesToHash() As Byte = Datos + bytesToHash = sha1Obj.ComputeHash(bytesToHash) + Dim strResult As String = "" + For Each b As Byte In bytesToHash + strResult += b.ToString("x2") + Next + Return strResult.ToUpper + End Function + 'Public Shared Sub FirmaPDF(cert As X509Certificate2, ClavePDF As String, ByVal PdfOrigen As Byte(), ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, RutaTmp As String, Optional EliminarFirmasAnteriores As Boolean = True, Optional SobreescribirCSP As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "") + ' FirmaPDF(cert, ClavePDF, New MemoryStream(PdfOrigen), pdfDestino, Razon, Contacto, Localizacion, EliminarFirmasAnteriores, SobreescribirCSP, ObviarSeguridad, NombreCampoFirma) + 'End Sub + + + ' CREADA POR JAVIER PACIOS LOPEZ + + + + Public Shared Sub FirmaPDF(ByVal bPfx As Byte(), ByVal ClavePFX As String, ByVal ClavePDF As String, ByRef PdfOrigen As Byte(), ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, Optional EliminarFirmasAnteriores As Boolean = True, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional ConvertirPDFA As Boolean = False, Optional PDFAObligatorio As Boolean = False) + Dim msOrigen = New MemoryStream(PdfOrigen) + Dim StreamPFX = New MemoryStream(bPfx) + FirmaPDF(StreamPFX, ClavePFX, ClavePDF, msOrigen, pdfDestino, Razon, Contacto, Localizacion, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, ConvertirPDFA, PDFAObligatorio) + End Sub + + Public Shared Sub FirmaPDF(ByVal StreamPFX As Stream, ByVal ClavePFX As String, ByVal ClavePDF As String, ByRef PdfOrigen As Byte(), ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, Optional EliminarFirmasAnteriores As Boolean = True, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional ConvertirPDFA As Boolean = False, Optional PDFAObligatorio As Boolean = False) + Dim msOrigen = New MemoryStream(PdfOrigen) + + FirmaPDF(StreamPFX, ClavePFX, ClavePDF, msOrigen, pdfDestino, Razon, Contacto, Localizacion, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, ConvertirPDFA, PDFAObligatorio) + End Sub + + Public Shared Sub FirmaPDF(ByVal StreamPFX As Stream, ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As String, ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, Optional EliminarFirmasAnteriores As Boolean = True, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional ConvertirPDFA As Boolean = False, Optional PDFAObligatorio As Boolean = False) + Dim msOrigen = New MemoryStream(System.IO.File.ReadAllBytes(PdfOrigen)) + FirmaPDF(StreamPFX, ClavePFX, ClavePDF, msOrigen, pdfDestino, Razon, Contacto, Localizacion, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, ConvertirPDFA, PDFAObligatorio) + End Sub + + Public Shared Sub FirmaPDF(ByVal StreamPFX As Stream, ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As String, ByVal pdfDestino As String, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, Optional EliminarFirmasAnteriores As Boolean = True, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional ConvertirPDFA As Boolean = False, Optional PDFAObligatorio As Boolean = False) + Dim msOrigen = New MemoryStream(System.IO.File.ReadAllBytes(PdfOrigen)) + Dim msDestino As New MemoryStream + FirmaPDF(StreamPFX, ClavePFX, ClavePDF, msOrigen, msDestino, Razon, Contacto, Localizacion, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, ConvertirPDFA, PDFAObligatorio) + System.IO.File.WriteAllBytes(pdfDestino, msDestino.ToArray) + End Sub + Public Shared Sub FirmaPDF(ByVal bPfx As Byte(), ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As Stream, ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, Optional EliminarFirmasAnteriores As Boolean = True, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional ConvertirPDFA As Boolean = False, Optional PDFAObligatorio As Boolean = False) + Dim mscert As New MemoryStream(bPfx) + FirmaPDF(mscert, ClavePFX, ClavePDF, PdfOrigen, pdfDestino, Razon, Contacto, Localizacion, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, ConvertirPDFA, PDFAObligatorio) + End Sub + Public Shared Sub FirmaPDF(ByVal StreamPFX As Stream, ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As Stream, ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, Optional EliminarFirmasAnteriores As Boolean = True, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional ConvertirPDFA As Boolean = False, Optional PDFAObligatorio As Boolean = False) + Try + Dim ms As New MemoryStream + + Dim pdfLectura As Stream + If ConvertirPDFA Then + + Try + pdfLectura = Utilidades.crearPDFA(PdfOrigen) + Catch ex As Exception + If PDFAObligatorio Then + Throw New Exception(ex.Message, ex) + Else + PdfOrigen.Position = 0 + pdfLectura = PdfOrigen + End If + End Try + Else + pdfLectura = PdfOrigen + End If + + Dim reader As New PdfReader(pdfLectura) + reader.SetUnethicalReading(ObviarSeguridad) + + + + Dim sp As New StampingProperties() + + If EliminarFirmasAnteriores = False Then + sp.UseAppendMode() + End If + + + + + + + If EliminarFirmasAnteriores = False Then + If ClavePDF <> "" Then + Dim bc = Encoding.ASCII.GetBytes(ClavePDF) + Dim props As WriterProperties = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + Dim writer As PdfWriter = New PdfWriter(ms, props) + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + pdfDoc.Close() + Dim rp As New ReaderProperties() + rp.SetPassword(bc) + reader = New PdfReader(New MemoryStream(ms.ToArray), rp) + reader.SetUnethicalReading(ObviarSeguridad) + End If + Else + Dim writer As PdfWriter + If ClavePDF <> "" Then + Dim bc = Encoding.ASCII.GetBytes(ClavePDF) + Dim props = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + writer = New PdfWriter(ms, props) + Else + writer = New PdfWriter(ms) + End If + + + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + Dim form = PdfAcroForm.GetAcroForm(pdfDoc, True) + form.FlattenFields() + pdfDoc.Close() + If ClavePDF = "" Then + reader = New PdfReader(New MemoryStream(ms.ToArray)) + Else + Dim pr As New ReaderProperties() + pr.SetPassword(Encoding.ASCII.GetBytes(ClavePDF)) + reader = New PdfReader(New MemoryStream(ms.ToArray), pr) + End If + reader.SetUnethicalReading(ObviarSeguridad) + End If + + + + Dim pdfSign As New PdfSigner(reader, pdfDestino, sp) + + + ' EN CASO DE QUE SEA PDFA COMPRUEBA QUE TENGA LA ANOTACIÓN F + If Utilidades.TieneMarcaPdfA(pdfLectura) Then + Dim pdfDoc1 As PdfDocument = pdfSign.GetDocument() + Utilidades.RepararAnotacionesSinF(pdfDoc1) + End If + + + If NombreCampoFirma <> "" Then + pdfSign.SetFieldName(NombreCampoFirma) + Else + pdfSign.SetFieldName(pdfSign.GetNewSigFieldName()) + End If + pdfSign.GetSignatureAppearance().SetReason(Razon).SetLocation(Localizacion).SetContact(Contacto) + + Dim cpassword = ClavePFX.ToCharArray + + + + + Dim [alias] As String = Nothing + Dim pk12 As Pkcs12Store = New Pkcs12Store(StreamPFX, cpassword) + + For Each a In pk12.Aliases + [alias] = (CType(a, String)) + + If pk12.IsKeyEntry([alias]) Then + Exit For + End If + Next + + Dim pk As IPrivateKey = New PrivateKeyBC(pk12.GetKey([alias]).Key) + Dim pks = New PrivateKeySignature(pk, DigestAlgorithms.SHA256) + + + Dim ce As X509CertificateEntry() = pk12.GetCertificateChain([alias]) + Dim chain = New IX509Certificate(ce.Length - 1) {} + + For k As Integer = 0 To ce.Length - 1 + chain(k) = New X509CertificateBC(ce(k).Certificate) + Next + + ' Dim pks = GetPrivateKeySignature(StreamPFX, cpassword) + ' Dim chain As IX509Certificate() = GetCertificateChain(StreamPFX, cpassword) + + + Dim OCSPVerifier = New OCSPVerifier(Nothing, Nothing) + Dim ocspClient = New OcspClientBouncyCastle(OCSPVerifier) + Dim crlClients = New List(Of ICrlClient) + + pdfSign.SignDetached(pks, chain, crlClients, ocspClient, Nothing, 0, PdfSigner.CryptoStandard.CMS) + reader.Close() + + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + + + + Public Shared Sub FirmaPDF(cert As X509Certificate2, ClavePDF As String, ByVal PdfOrigen As String, ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, Optional EliminarFirmasAnteriores As Boolean = True, Optional SobreescribirCSP As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional ConvertirPDFA As Boolean = False, Optional PDFAObligatorio As Boolean = False) + Dim fs As New FileStream(PdfOrigen, FileMode.Open) + FirmaPDF(cert, ClavePDF, fs, pdfDestino, Razon, Contacto, Localizacion, EliminarFirmasAnteriores, SobreescribirCSP, ObviarSeguridad, NombreCampoFirma, ConvertirPDFA, PDFAObligatorio) + fs.Close() + End Sub + ' COMPROBADA + Public Shared Sub FirmaPDF(cert As X509Certificate2, ClavePDF As String, ByVal PdfOrigen As Stream, ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, Optional EliminarFirmasAnteriores As Boolean = True, Optional SobreescribirCSP As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional ConvertirPDFA As Boolean = False, Optional PDFAObligatorio As Boolean = False) + Try + If SobreescribirCSP Then + Dim rsa As RSACryptoServiceProvider = CType(cert.PrivateKey, RSACryptoServiceProvider) + Dim csparams As New CspParameters + csparams.KeyContainerName = "TECNOSIS-" & Guid.NewGuid.ToString + Dim rsa2 As New RSACryptoServiceProvider(csparams) + rsa2.ImportParameters(rsa.ExportParameters(True)) + cert.PrivateKey = rsa2 + End If + + Dim pdfLectura As Stream + + If ConvertirPDFA Then + + Try + pdfLectura = Utilidades.crearPDFA(PdfOrigen) + Catch ex As Exception + If PDFAObligatorio Then + Throw New Exception(ex.Message, ex) + Else + PdfOrigen.Position = 0 + pdfLectura = PdfOrigen + End If + End Try + Else + pdfLectura = PdfOrigen + End If + + Dim reader As New PdfReader(pdfLectura) + reader.SetUnethicalReading(ObviarSeguridad) + + Dim sp As New StampingProperties() + If EliminarFirmasAnteriores = False Then + sp.UseAppendMode() + If ClavePDF <> "" Then + Dim bc = Encoding.ASCII.GetBytes(ClavePDF) + Dim props As WriterProperties = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + Using ms = New MemoryStream() + Dim writer As PdfWriter = New PdfWriter(ms, props) + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + pdfDoc.Close() + reader.Close() + writer.Close() + Dim rp As New ReaderProperties() + rp.SetPassword(bc) + reader = New PdfReader(New MemoryStream(ms.ToArray), rp) + reader.SetUnethicalReading(ObviarSeguridad) + End Using + End If + Else + Using ms = New MemoryStream() + Dim writer As PdfWriter = New PdfWriter(ms) + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + Dim form = PdfAcroForm.GetAcroForm(pdfDoc, True) + form.FlattenFields() + pdfDoc.Close() + reader.Close() + writer.Close() + If ClavePDF = "" Then + reader = New PdfReader(New MemoryStream(ms.ToArray)) + Else + Dim pr As New ReaderProperties() + pr.SetPassword(Encoding.ASCII.GetBytes(ClavePDF)) + reader = New PdfReader(New MemoryStream(ms.ToArray), pr) + End If + reader.SetUnethicalReading(ObviarSeguridad) + End Using + End If + + + + Dim bccert As Org.BouncyCastle.X509.X509Certificate = New Org.BouncyCastle.X509.X509Certificate(Org.BouncyCastle.Asn1.X509.X509CertificateStructure.GetInstance(cert.RawData)) + Dim chain As IX509Certificate() = {New TSpdf.Bouncycastle.Cert.X509CertificateBC(bccert)} + Dim signature As X509Certificate2Signature = New X509Certificate2Signature(cert, "SHA256") + Dim pdfSign As New PdfSigner(reader, pdfDestino, sp) + If NombreCampoFirma <> "" Then + pdfSign.SetFieldName(NombreCampoFirma) + Else + pdfSign.SetFieldName(pdfSign.GetNewSigFieldName()) + End If + pdfSign.GetSignatureAppearance().SetReason(Razon).SetLocation(Localizacion).SetContact(Contacto) + pdfSign.SignDetached(signature, chain, Nothing, Nothing, Nothing, 0, PdfSigner.CryptoStandard.CMS) + reader.Close() + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + + + + + Public Shared Sub FirmaPDFVisible(bPfx As Byte(), ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As Byte(), ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, ByVal listadoTextos As List(Of DatosTextos), ByVal listadoImagenes As List(Of DatosImagenFirma), Optional listadoFuentes As List(Of DatosFuente) = Nothing, Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional DatosCampoFirma As DatosNuevoCampoFirma = Nothing, Optional BloquearDocumento As Boolean = False) + Dim StreamPfx As New MemoryStream(bPfx) + Dim ms As New MemoryStream + Dim fso = New MemoryStream(PdfOrigen) + FirmaPDFVisible(StreamPfx, ClavePFX, ClavePDF, fso, ms, Razon, Contacto, Localizacion, listadoTextos, listadoImagenes, listadoFuentes, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, DatosCampoFirma, BloquearDocumento) + End Sub + + + + Public Shared Sub FirmaPDFVisible(bPfx As Byte(), ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As String, ByVal pdfDestino As String, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, ByVal listadoTextos As List(Of DatosTextos), ByVal listadoImagenes As List(Of DatosImagenFirma), Optional listadoFuentes As List(Of DatosFuente) = Nothing, Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional DatosCampoFirma As DatosNuevoCampoFirma = Nothing, Optional BloquearDocumento As Boolean = False) + Dim StreamPfx As New MemoryStream(bPfx) + Dim fso = New MemoryStream(System.IO.File.ReadAllBytes(PdfOrigen)) + Dim fs As New FileStream(pdfDestino, FileMode.Create) + FirmaPDFVisible(StreamPfx, ClavePFX, ClavePDF, fso, fs, Razon, Contacto, Localizacion, listadoTextos, listadoImagenes, listadoFuentes, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, DatosCampoFirma, BloquearDocumento) + fs.Close() + End Sub + + Public Shared Sub FirmaPDFVisible(bPfx As Byte(), ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As String, ByVal pdfDestino As String, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, Imagen As Byte(), Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional DatosCampoFirma As DatosNuevoCampoFirma = Nothing, Optional BloquearDocumento As Boolean = False) + Dim StreamPfx As New MemoryStream(bPfx) + Dim fso = New MemoryStream(System.IO.File.ReadAllBytes(PdfOrigen)) + Dim fs As New FileStream(pdfDestino, FileMode.Create) + FirmaPDFVisible(StreamPfx, ClavePFX, ClavePDF, fso, fs, Razon, Contacto, Localizacion, Imagen, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, DatosCampoFirma, BloquearDocumento) + fs.Close() + End Sub + + + Public Shared Sub FirmaPDFVisible(ByVal StreamPFX As Stream, ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As Stream, ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, Imagen As Byte(), Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional DatosCampoFirma As DatosNuevoCampoFirma = Nothing, Optional BloquearDocumento As Boolean = False) + Try + Dim ms As New MemoryStream + Dim reader As New PdfReader(PdfOrigen) + reader.SetUnethicalReading(ObviarSeguridad) + Dim sp As New StampingProperties() + 'Dim ListaCampos() As String + 'If NombreCampoFirma = "" Then + ' If BloquearDocumento Then + ' Try + ' Dim pdfDoc As PdfDocument = New PdfDocument(reader) + ' Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + ' ListaCampos = pdfAcrof.GetAllFormFields.Select(Function(x) x.Key).ToArray + ' pdfDoc.Close() + ' Catch + ' End Try + ' PdfOrigen.Seek(0, 0) + ' reader = New PdfReader(PdfOrigen) + ' End If + 'Else + ' Dim pdfDoc As PdfDocument = New PdfDocument(reader) + ' Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + ' If BloquearDocumento Then + ' ListaCampos = pdfAcrof.GetAllFormFields.Select(Function(x) x.Key).ToArray + ' End If + ' 'medidas = pdfAcrof.GetField(NombreCampoFirma).GetWidgets(0).GetRectangle.ToRectangle + ' pdfDoc.Close() + ' PdfOrigen.Seek(0, 0) + ' reader = New PdfReader(PdfOrigen) + 'End If + If EliminarFirmasAnteriores = False Then + sp.UseAppendMode() + If ClavePDF <> "" Then + Dim bc = Encoding.ASCII.GetBytes(ClavePDF) + Dim props As WriterProperties = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + Dim writer As PdfWriter = New PdfWriter(ms, props) + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + ' If NombreCampoFirma <> "" Then medidas = pdfAcrof.GetField(NombreCampoFirma).GetWidgets(0).GetRectangle.ToRectangle + pdfDoc.Close() + Dim rp As New ReaderProperties() + rp.SetPassword(bc) + reader = New PdfReader(New MemoryStream(ms.ToArray), rp) + + End If + reader.SetUnethicalReading(ObviarSeguridad) + + Else + Dim writer As PdfWriter + If ClavePDF <> "" Then + Dim bc = Encoding.ASCII.GetBytes(ClavePDF) + Dim props = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + writer = New PdfWriter(ms, props) + Else + writer = New PdfWriter(ms) + End If + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + pdfAcrof.FlattenFields() + pdfDoc.Close() + If ClavePDF = "" Then + reader = New PdfReader(New MemoryStream(ms.ToArray)) + Else + Dim pr As New ReaderProperties() + pr.SetPassword(Encoding.ASCII.GetBytes(ClavePDF)) + reader = New PdfReader(New MemoryStream(ms.ToArray), pr) + End If + reader.SetUnethicalReading(ObviarSeguridad) + End If + + + + + + Dim cpassword = ClavePFX.ToCharArray + Dim [alias] As String = Nothing + Dim pk12 As Pkcs12Store = New Pkcs12Store(StreamPFX, cpassword) + + For Each a In pk12.Aliases + [alias] = (CType(a, String)) + + If pk12.IsKeyEntry([alias]) Then + Exit For + End If + Next + + Dim pk As IPrivateKey = New PrivateKeyBC(pk12.GetKey([alias]).Key) + Dim pks = New PrivateKeySignature(pk, DigestAlgorithms.SHA256) + + Dim ce As X509CertificateEntry() = pk12.GetCertificateChain([alias]) + Dim chain = New IX509Certificate(ce.Length - 1) {} + + For k As Integer = 0 To ce.Length - 1 + chain(k) = New X509CertificateBC(ce(k).Certificate) + Next + + + + Dim OCSPVerifier = New OCSPVerifier(Nothing, Nothing) + Dim ocspClient = New OcspClientBouncyCastle(OCSPVerifier) + Dim crlClients = New List(Of ICrlClient) + Dim listadoDatosCertificado = ce(0).Certificate.SubjectDN.GetValueList + Dim nombre = listadoDatosCertificado(4).ToString + + + Dim pdfSign As New PdfSigner(reader, pdfDestino, sp) + If NombreCampoFirma = "" Then + pdfSign.SetFieldName(DatosCampoFirma.NombreCampo) + Else + pdfSign.SetFieldName(NombreCampoFirma) + End If + Dim idata As IO.Image.ImageData = IO.Image.ImageDataFactory.Create(Imagen) + If NombreCampoFirma = "" Then + Dim medidas = New Kernel.Geom.Rectangle(DatosCampoFirma.CoordenadaX, DatosCampoFirma.CoordenadaY, DatosCampoFirma.Ancho, DatosCampoFirma.Alto) + Dim canvas2 As New PdfCanvas(pdfSign.GetDocument.GetFirstPage.NewContentStreamBefore(), pdfSign.GetDocument.GetFirstPage().GetResources(), pdfSign.GetDocument) + Dim signLayoutCanvas = New Canvas(canvas2, medidas) + pdfSign.GetSignatureAppearance().SetReason(Razon).SetLocation(Localizacion).SetContact(Contacto).SetPageRect(medidas).SetImage(idata).SetLayer2Text("") + Else + pdfSign.GetSignatureAppearance().SetReason(Razon).SetLocation(Localizacion).SetContact(Contacto).SetImage(idata).SetLayer2Text("") + End If + + If BloquearDocumento Then + + pdfSign.SetCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED) + + End If + pdfSign.SignDetached(pks, chain, crlClients, ocspClient, Nothing, 0, PdfSigner.CryptoStandard.CADES) + + + reader.Close() + + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + + Public Shared Sub FirmaPDFVisible(ByVal StreamPFX As Stream, ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As Stream, ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, ByVal listadoTextos As List(Of DatosTextos), ByVal listadoImagenes As List(Of DatosImagenFirma), Optional listadoFuentes As List(Of DatosFuente) = Nothing, Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional DatosCampoFirma As DatosNuevoCampoFirma = Nothing, Optional BloquearDocumento As Boolean = False) + Try + Dim ms As New MemoryStream + Dim reader As New PdfReader(PdfOrigen) + reader.SetUnethicalReading(ObviarSeguridad) + Dim sp As New StampingProperties() + ' Dim ListaCampos() As String + Dim medidas As Kernel.Geom.Rectangle + If NombreCampoFirma = "" Then + medidas = New Kernel.Geom.Rectangle(DatosCampoFirma.CoordenadaX, DatosCampoFirma.CoordenadaY, DatosCampoFirma.Ancho, DatosCampoFirma.Alto) + 'If BloquearDocumento Then + ' Try + ' Dim pdfDoc As PdfDocument = New PdfDocument(reader) + ' Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + ' ListaCampos = pdfAcrof.GetAllFormFields.Select(Function(x) x.Key).ToArray + ' pdfDoc.Close() + ' Catch + ' End Try + ' PdfOrigen.Seek(0, 0) + ' reader = New PdfReader(PdfOrigen) + 'End If + Else + Dim pdfDoc As PdfDocument = New PdfDocument(reader) + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + Dim cf = pdfAcrof.GetField(NombreCampoFirma) + If cf Is Nothing Then + medidas = New Kernel.Geom.Rectangle(DatosCampoFirma.CoordenadaX, DatosCampoFirma.CoordenadaY, DatosCampoFirma.Ancho, DatosCampoFirma.Alto) + Else + medidas = cf.GetWidgets(0).GetRectangle.ToRectangle + End If + pdfDoc.Close() + PdfOrigen.Seek(0, 0) + reader = New PdfReader(PdfOrigen) + End If + If EliminarFirmasAnteriores = False Then + sp.UseAppendMode() + If ClavePDF <> "" Then + Dim bc = Encoding.ASCII.GetBytes(ClavePDF) + Dim props As WriterProperties = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + Dim writer As PdfWriter = New PdfWriter(ms, props) + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + ' If NombreCampoFirma <> "" Then medidas = pdfAcrof.GetField(NombreCampoFirma).GetWidgets(0).GetRectangle.ToRectangle + pdfDoc.Close() + Dim rp As New ReaderProperties() + rp.SetPassword(bc) + reader = New PdfReader(New MemoryStream(ms.ToArray), rp) + + End If + reader.SetUnethicalReading(ObviarSeguridad) + + Else + Dim writer As PdfWriter + If ClavePDF <> "" Then + Dim bc = Encoding.ASCII.GetBytes(ClavePDF) + Dim props = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + writer = New PdfWriter(ms, props) + Else + writer = New PdfWriter(ms) + End If + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + pdfAcrof.FlattenFields() + pdfDoc.Close() + If ClavePDF = "" Then + reader = New PdfReader(New MemoryStream(ms.ToArray)) + Else + Dim pr As New ReaderProperties() + pr.SetPassword(Encoding.ASCII.GetBytes(ClavePDF)) + reader = New PdfReader(New MemoryStream(ms.ToArray), pr) + End If + reader.SetUnethicalReading(ObviarSeguridad) + End If + + Dim cpassword = ClavePFX.ToCharArray + Dim [alias] As String = Nothing + Dim pk12 As Pkcs12Store = New Pkcs12Store(StreamPFX, cpassword) + + For Each a In pk12.Aliases + [alias] = (CType(a, String)) + + If pk12.IsKeyEntry([alias]) Then + Exit For + End If + Next + + Dim pk As IPrivateKey = New PrivateKeyBC(pk12.GetKey([alias]).Key) + Dim pks = New PrivateKeySignature(pk, DigestAlgorithms.SHA256) + + Dim ce As X509CertificateEntry() = pk12.GetCertificateChain([alias]) + Dim chain = New IX509Certificate(ce.Length - 1) {} + + For k As Integer = 0 To ce.Length - 1 + chain(k) = New X509CertificateBC(ce(k).Certificate) + Next + + ' Dim pks = GetPrivateKeySignature(StreamPFX, cpassword) + ' Dim chain As IX509Certificate() = GetCertificateChain(StreamPFX, cpassword) + + Dim OCSPVerifier = New OCSPVerifier(Nothing, Nothing) + Dim ocspClient = New OcspClientBouncyCastle(OCSPVerifier) + Dim crlClients = New List(Of ICrlClient) + Dim listadoDatosCertificado = ce(0).Certificate.SubjectDN.GetValueList + Dim nombre = listadoDatosCertificado(4).ToString + + Dim pdfSign As New PdfSigner(reader, pdfDestino, sp) + If NombreCampoFirma = "" Then + pdfSign.SetFieldName(DatosCampoFirma.NombreCampo) + Else + pdfSign.SetFieldName(NombreCampoFirma) + End If + 'Dim canvas2 As New PdfCanvas(pdfSign.GetDocument.GetFirstPage.NewContentStreamBefore(), pdfSign.GetDocument.GetFirstPage().GetResources(), pdfSign.GetDocument) + + 'Dim signLayoutCanvas = New Canvas(canvas2, medidas) + ' medidas.DecreaseHeight(30) + Dim bmp = Imagenes.crearBitMapFirmas(listadoTextos, listadoImagenes, listadoFuentes, medidas) + ' File.WriteAllBytes("c:\tmp\1.bmp", Imagenes.BitMapAByteArray(bmp)) + + ' bmp = New Bitmap(bmp, bmp.Width, bmp.Height - 640) + + Dim imagenUrl = Imagenes.BitMapAByteArray(bmp) + ' File.WriteAllBytes("c:\tmp\2.bmp", Imagenes.BitMapAByteArray(bmp)) + + + Dim imagen As IO.Image.ImageData = IO.Image.ImageDataFactory.Create(imagenUrl) + pdfSign.GetSignatureAppearance().SetReason(Razon).SetLocation(Localizacion).SetContact(Contacto).SetPageRect(medidas).SetImage(imagen).SetLayer2Text("") + If BloquearDocumento Then + 'Dim sfl = New PdfSigFieldLock + 'sfl.SetDocumentPermissions(PdfSigFieldLock.LockPermissions.NO_CHANGES_ALLOWED) + 'If ListaCampos IsNot Nothing AndAlso ListaCampos.Length > 0 Then + ' sfl.SetFieldLock(PdfSigFieldLock.LockAction.INCLUDE, ListaCampos) + ' sfl.GetPdfObject.Remove(PdfName.Fields) + 'End If + 'pdfSign.SetFieldLockDict(sfl) + pdfSign.SetCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED) + End If + pdfSign.SignDetached(pks, chain, crlClients, ocspClient, Nothing, 0, PdfSigner.CryptoStandard.CADES) + + reader.Close() + + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + + Public Shared Sub FirmaPDFVisibleSimple(bPfx As Byte(), ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As Byte(), ByRef pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, TituloFirmante As String, DatosCampoFirma As DatosNuevoCampoFirma, Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional BloquearDocumento As Boolean = False) + Dim StreamPfx As New MemoryStream(bPfx) + Dim fso = New MemoryStream(PdfOrigen) + FirmaPDFVisibleSimple(StreamPfx, ClavePFX, ClavePDF, fso, pdfDestino, Razon, Contacto, Localizacion, TituloFirmante, DatosCampoFirma, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, BloquearDocumento) + End Sub + + + Public Shared Sub FirmaPDFVisibleSimple(ByVal StreamPFX As Stream, ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As Stream, ByRef pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, TituloFirmante As String, DatosCampoFirma As DatosNuevoCampoFirma, Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional BloquearDocumento As Boolean = False) + Try + Dim ms As New MemoryStream + Dim reader As New PdfReader(PdfOrigen) + reader.SetUnethicalReading(ObviarSeguridad) + Dim sp As New StampingProperties() + ' Dim ListaCampos() As String + Dim medidas As Kernel.Geom.Rectangle + If NombreCampoFirma = "" Then + medidas = New Kernel.Geom.Rectangle(DatosCampoFirma.CoordenadaX, DatosCampoFirma.CoordenadaY, DatosCampoFirma.Ancho, DatosCampoFirma.Alto) + Else + Dim pdfDoc As PdfDocument = New PdfDocument(reader) + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + Dim cf = pdfAcrof.GetField(NombreCampoFirma) + If cf Is Nothing Then + medidas = New Kernel.Geom.Rectangle(DatosCampoFirma.CoordenadaX, DatosCampoFirma.CoordenadaY, DatosCampoFirma.Ancho, DatosCampoFirma.Alto) + Else + medidas = cf.GetWidgets(0).GetRectangle.ToRectangle + End If + pdfDoc.Close() + PdfOrigen.Seek(0, 0) + reader = New PdfReader(PdfOrigen) + End If + If EliminarFirmasAnteriores = False Then + sp.UseAppendMode() + If ClavePDF <> "" Then + Dim bc = Encoding.ASCII.GetBytes(ClavePDF) + Dim props As WriterProperties = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + Dim writer As PdfWriter = New PdfWriter(ms, props) + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + pdfDoc.Close() + Dim rp As New ReaderProperties() + rp.SetPassword(bc) + reader = New PdfReader(New MemoryStream(ms.ToArray), rp) + + End If + reader.SetUnethicalReading(ObviarSeguridad) + + Else + Dim writer As PdfWriter + If ClavePDF <> "" Then + Dim bc = Encoding.ASCII.GetBytes(ClavePDF) + Dim props = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + writer = New PdfWriter(ms, props) + Else + writer = New PdfWriter(ms) + End If + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + pdfAcrof.FlattenFields() + pdfDoc.Close() + If ClavePDF = "" Then + reader = New PdfReader(New MemoryStream(ms.ToArray)) + Else + Dim pr As New ReaderProperties() + pr.SetPassword(Encoding.ASCII.GetBytes(ClavePDF)) + reader = New PdfReader(New MemoryStream(ms.ToArray), pr) + End If + reader.SetUnethicalReading(ObviarSeguridad) + End If + + Dim cpassword = ClavePFX.ToCharArray + Dim [alias] As String = Nothing + Dim pk12 As Pkcs12Store = New Pkcs12Store(StreamPFX, cpassword) + + For Each a In pk12.Aliases + [alias] = (CType(a, String)) + + If pk12.IsKeyEntry([alias]) Then + Exit For + End If + Next + + Dim pk As IPrivateKey = New PrivateKeyBC(pk12.GetKey([alias]).Key) + Dim pks = New PrivateKeySignature(pk, DigestAlgorithms.SHA256) + + Dim ce As X509CertificateEntry() = pk12.GetCertificateChain([alias]) + Dim chain = New IX509Certificate(ce.Length - 1) {} + + For k As Integer = 0 To ce.Length - 1 + chain(k) = New X509CertificateBC(ce(k).Certificate) + Next + + + Dim OCSPVerifier = New OCSPVerifier(Nothing, Nothing) + Dim ocspClient = New OcspClientBouncyCastle(OCSPVerifier) + Dim crlClients = New List(Of ICrlClient) + Dim listadoDatosCertificado = ce(0).Certificate.SubjectDN.GetValueList + Dim nombre = listadoDatosCertificado(4).ToString + + Dim pdfSign As New PdfSigner(reader, pdfDestino, sp) + If NombreCampoFirma = "" Then + pdfSign.SetFieldName(DatosCampoFirma.NombreCampo) + Else + pdfSign.SetFieldName(NombreCampoFirma) + End If + Dim pdffh As PdfFont = PdfFontFactory.CreateFont(StandardFonts.HELVETICA, PdfEncodings.UTF8) + + + + + Dim pdfd As PdfDocument = pdfSign.GetDocument() + Dim appearance As PdfSignatureAppearance = pdfSign.GetSignatureAppearance().SetReason(Razon).SetLocation(Localizacion).SetContact(Contacto).SetPageRect(medidas) + appearance.SetPageNumber(1) + appearance.SetReuseAppearance(False) + + Dim layer2 = appearance.GetLayer2() + Dim canvas As New PdfCanvas(layer2, pdfd) + Dim layoutCanvas As New Canvas(canvas, medidas) + + Dim font As PdfFont = PdfFontFactory.CreateFont(StandardFonts.HELVETICA) + Dim f = DateTime.Today.ToString("dd/MM/yyyy") + + layoutCanvas.SetFont(font) _ + .SetFontSize(10) _ + .ShowTextAligned("Firmado Digitalmente Por: " & TituloFirmante, 10, medidas.GetHeight() - 20, TextAlignment.LEFT) _ + .ShowTextAligned("Fecha: " & f, 10, medidas.GetHeight() - 35, TextAlignment.LEFT) _ + .ShowTextAligned("Motivo: " & Razon, 10, medidas.GetHeight() - 50, TextAlignment.LEFT) + + + If BloquearDocumento Then + pdfSign.SetCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED) + End If + pdfSign.SignDetached(pks, chain, crlClients, ocspClient, Nothing, 0, PdfSigner.CryptoStandard.CADES) + + reader.Close() + + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + + Public Shared Function ObtieneMedidasCampo(Fichero As String, NombreCampo As String) As Kernel.Geom.Rectangle + Dim reader As New PdfReader(Fichero) + Return ObtieneMedidasCampo(reader, NombreCampo) + End Function + + Public Shared Function ObtieneMedidasCampo(reader As PdfReader, NombreCampo As String) As Kernel.Geom.Rectangle + Dim pdfDoc As PdfDocument = New PdfDocument(reader) + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + Dim cf = pdfAcrof.GetField(NombreCampo) + Return cf.GetWidgets(0).GetRectangle.ToRectangle + End Function + + Public Class DatosNuevoCampoFirma + Public NombreCampo As String + Public CoordenadaX As Single + Public CoordenadaY As Single + Public Ancho As Single + Public Alto As Single + End Class + + Public Shared Sub AseguraPdf(ByVal FicheroPdf As String, ByVal passwordUser As String, ByVal passwordOwner As String, Permisos As Integer) + Dim srcBytes = System.IO.File.ReadAllBytes(FicheroPdf) + Dim up As Byte() = Nothing + Dim op As Byte() = Nothing + If passwordUser IsNot Nothing AndAlso passwordUser <> "" Then up = Encoding.ASCII.GetBytes(passwordUser) + If passwordOwner IsNot Nothing AndAlso passwordOwner <> "" Then op = Encoding.ASCII.GetBytes(passwordOwner) + Dim reader As PdfReader = New PdfReader(New MemoryStream(srcBytes)) + Dim props As WriterProperties = New WriterProperties().SetStandardEncryption(up, op, Permisos, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + + Using memoryStream = New MemoryStream() + Dim writer As PdfWriter = New PdfWriter(memoryStream, props) + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + pdfDoc.Close() + reader.Close() + System.IO.File.Delete(FicheroPdf) + System.IO.File.WriteAllBytes(FicheroPdf, memoryStream.ToArray) + End Using + End Sub + Public Shared Sub AseguraPdf(ByVal PdfOrigen As String, ByVal PdfDestino As String, ByVal passwordUser As String, ByVal passwordOwner As String, Permisos As Integer) + Dim srcBytes = System.IO.File.ReadAllBytes(PdfOrigen) + Dim up As Byte() = Nothing + Dim op As Byte() = Nothing + If passwordUser IsNot Nothing AndAlso passwordUser <> "" Then up = Encoding.ASCII.GetBytes(passwordUser) + If passwordOwner IsNot Nothing AndAlso passwordOwner <> "" Then op = Encoding.ASCII.GetBytes(passwordOwner) + Dim reader As PdfReader = New PdfReader(New MemoryStream(srcBytes)) + Dim props As WriterProperties = New WriterProperties().SetStandardEncryption(up, op, Permisos, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + + Using memoryStream = New MemoryStream() + Dim writer As PdfWriter = New PdfWriter(memoryStream, props) + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + pdfDoc.Close() + reader.Close() + If System.IO.File.Exists(PdfDestino) Then System.IO.File.Delete(PdfDestino) + System.IO.File.WriteAllBytes(PdfDestino, memoryStream.ToArray) + End Using + End Sub + Public Shared Function CompruebaFirmaPDF(arrayCertsPadresBin As Byte()(), FicheroPdf As String, NIF As String, Optional CompruebaCoberturaFirma As Boolean = False) As Byte() + Try + Dim ms = New MemoryStream(System.IO.File.ReadAllBytes(FicheroPdf)) + Return CompruebaFirmaPDF(arrayCertsPadresBin, ms, NIF, CompruebaCoberturaFirma) + Catch ex As Exception + Throw New Exception("Error comprobando firma del fichero " & FicheroPdf & ". " & ex.Message, ex) + End Try + End Function + Public Shared Function CompruebaFirmaPDF(arrayCertsPadresBin As Byte()(), pdf As Stream, NIF As String, Optional CompruebaCoberturaFirma As Boolean = False, Optional ByVal CompruebaTodasLasFirmas As Boolean = False) As Byte() + Try + Dim bRespuestaOCSP() As Byte = Nothing + Dim reader = New PdfReader(pdf) + Dim pdfd = New PdfDocument(reader) + Dim su = New SignatureUtil(pdfd) + Dim nombres = su.GetSignatureNames + If nombres.Count = 0 Then Throw New Exception("El documento no contiene ninguna firma digital") + Dim CifComprobado As Boolean = False + Dim Nifencontrado As Boolean = False + Dim DIEncontrados As String = "" + For Each n In nombres + Dim pk = su.ReadSignatureData(n) + If Not pk.VerifySignatureIntegrityAndAuthenticity Then Throw New Exception("Alguna firma es incorrecta o el documento fue modificado") + + Dim pc = pk.GetSigningCertificate + Dim certbin = New X509Certificate2(pc.GetEncoded).Export(X509ContentType.Cert) + Dim iPadre As Integer = UtilsCert.BuscarCertPadre(certbin, arrayCertsPadresBin) + If iPadre < 0 AndAlso CompruebaTodasLasFirmas Then Throw New Exception("Certificado no admitido") + Dim dni As String = UtilsCert.ObtenerValorAtributoDN(certbin, "2.5.4.5") + If dni.StartsWith("IDCES-") Then dni = dni.Split("-"c)(1) + If CompruebaCoberturaFirma AndAlso dni.ToUpper = NIF.ToUpper AndAlso Not su.SignatureCoversWholeDocument(n) Then Throw New Exception("El documento fue modificado después de alguna firma, aunque esta es correcta") + DIEncontrados &= dni & "," + Dim msjInvalidezCert As String = "" + Dim validoCert As Boolean = UtilsCert.ValidoCertSinRevoc(certbin, arrayCertsPadresBin, DateTime.UtcNow, msjInvalidezCert) + If Not validoCert Then Throw New Exception("Certificado Inválido: " & msjInvalidezCert) + Dim infoRev As UtilsCert.InfoRevoc = UtilsCert.ConsultarInfoRevoc(certbin, arrayCertsPadresBin) + If NIF.ToLower = dni.ToLower Then + If infoRev.RespuestaEfectiva AndAlso infoRev.RespuestaOCSPBin IsNot Nothing Then + bRespuestaOCSP = infoRev.RespuestaOCSPBin + End If + Nifencontrado = True + End If + If infoRev.EstadoRevoc <> UtilsCert.EstadoRevocEnum.NoRevocado Then + Throw New Exception("No se puede confiar en el certificado. Estado de revocación: " & UtilsCert.EstadoRevocToString(infoRev.EstadoRevoc)) + End If + Next + If Nifencontrado = False Then Throw New Exception("El documento no ha sido firmado por el mismo nif Requerido. Documentos de identidad encontrados: " & DIEncontrados.TrimEnd(",")) + Return bRespuestaOCSP + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Function + Public Shared Sub CompruebaFirmaPDF(arrayCertsPadresBin As Byte()(), pdf As String, ListaDNIs As List(Of String), Optional IndicesCertPadresRepresentacion As List(Of Integer) = Nothing, Optional CompruebaTodasLasFirmas As Boolean = False, Optional CompruebaCoberturaFirma As Boolean = False) + Try + Dim ms As MemoryStream = New MemoryStream(System.IO.File.ReadAllBytes(pdf)) + CompruebaFirmaPDF(arrayCertsPadresBin, ms, ListaDNIs, IndicesCertPadresRepresentacion, CompruebaTodasLasFirmas, CompruebaCoberturaFirma) + Catch ex As Exception + Throw New Exception("Error comprobando firma del fichero " & pdf & ". " & ex.Message, ex) + End Try + End Sub + Public Shared Sub CompruebaFirmaPDF(arrayCertsPadresBin As Byte()(), pdf As Stream, ListaDNIs As List(Of String), Optional IndicesCertPadresRepresentacion As List(Of Integer) = Nothing, Optional CompruebaTodasLasFirmas As Boolean = False, Optional CompruebaCoberturaFirma As Boolean = False) + Dim reader As PdfReader = Nothing + Try + Dim bRespuestaOCSP() As Byte = Nothing + reader = New PdfReader(pdf) + Dim pdfd = New PdfDocument(reader) + Dim su = New SignatureUtil(pdfd) + Dim nombres = su.GetSignatureNames + If nombres.Count = 0 Then Throw New Exception("El documento no contiene ninguna firma digital") + Dim CifComprobado As Boolean = False + Dim Nifencontrado As Boolean = False + For Each n In nombres + Dim pk = su.ReadSignatureData(n) + Dim pc = pk.GetSigningCertificate + Dim certbin = New X509Certificate2(pc.GetEncoded).Export(X509ContentType.Cert) + Dim iPadre As Integer = UtilsCert.BuscarCertPadre(certbin, arrayCertsPadresBin) + Dim dni As String = "" + If IndicesCertPadresRepresentacion IsNot Nothing AndAlso IndicesCertPadresRepresentacion.Contains(iPadre) Then + Try + dni = UtilsCert.ObtenerValorAtributoDN(certbin, "2.5.4.97") + If dni.StartsWith("VATES-") Then dni = dni.Split("-"c)(1) + Catch + End Try + Else + Try + dni = UtilsCert.ObtenerValorAtributoDN(certbin, "2.5.4.5") + If dni.StartsWith("IDCES-") Then dni = dni.Split("-"c)(1) + Catch + End Try + End If + Dim sdniorep = ListaDNIs.FirstOrDefault(Function(x) x.Contains(dni)) + If sdniorep IsNot Nothing Then + If Not pk.VerifySignatureIntegrityAndAuthenticity Then Throw New Exception("La firma es incorrecta o el documento fue modificado") + If CompruebaCoberturaFirma AndAlso Not su.SignatureCoversWholeDocument(n) Then Throw New Exception("Aunque la firma es correcta, el documento fue modificado después de alguna firma") + If iPadre < 0 Then Throw New Exception("Certificado no admitido") + ListaDNIs.Remove(sdniorep) + Dim msjInvalidezCert As String = "" + Dim validoCert As Boolean = UtilsCert.ValidoCertSinRevoc(certbin, arrayCertsPadresBin, DateTime.UtcNow, msjInvalidezCert) + If Not validoCert Then Throw New Exception("Certificado Inválido: " & msjInvalidezCert) + Dim infoRev As UtilsCert.InfoRevoc = UtilsCert.ConsultarInfoRevoc(certbin, arrayCertsPadresBin) + If infoRev.RespuestaEfectiva AndAlso infoRev.RespuestaOCSPBin IsNot Nothing Then + bRespuestaOCSP = infoRev.RespuestaOCSPBin + End If + If infoRev.EstadoRevoc <> UtilsCert.EstadoRevocEnum.NoRevocado Then + Throw New Exception("No se puede confiar en el certificado. Estado de revocación: " & UtilsCert.EstadoRevocToString(infoRev.EstadoRevoc)) + End If + Else + If CompruebaTodasLasFirmas Then + If Not pk.VerifySignatureIntegrityAndAuthenticity Then Throw New Exception("La firma es incorrecta o el documento fue modificado") + If CompruebaCoberturaFirma AndAlso Not su.SignatureCoversWholeDocument(n) Then Throw New Exception("Aunque la firma es correcta, el documento fue modificado después de alguna firma") + If iPadre < 0 Then Throw New Exception("Certificado no admitido") + End If + End If + Next + If ListaDNIs.Count > 0 Then Throw New Exception("Falta alguna firma de las exigidas o de sus representantes en el fichero") + Catch ex As Exception + Throw New Exception(ex.Message, ex) + Finally + If reader IsNot Nothing Then + reader.Close() + End If + End Try + End Sub + Public Shared Sub ValidarAutoridadesFNMT(ByVal raizFnmtBin As Byte(), + ByVal acFnmtUsuariosBin As Byte(), + ByVal acFnmtRepresentacionBin As Byte()) + + '----------------------------------------------------------------------------------------------- + ' Valida las Autoridades de Certificación de la FNMT permitidas (PASO 1 DEL PROCESO GENERAL). + ' La validación se realiza en la FECHA ACTUAL. + ' Eleva EXCEPCIÓN si hay algún problema. + '----------------------------------------------------------------------------------------------- + ' 1. Validar las Autoridades de Certificación (ACs) en las que confiamos: + ' 1.1 Validar las Raíces con ellas mismas (sin analizar revocación), y + ' opcionalmente comprobar que sus hashes SHA-1 coinciden con los precableados + ' (para evitar que las Raíces sean sustituidas en la Base de Datos). + ' 1.2 Validar las ACs intermedias con sus padres correspondientes, y comprobar su revocación. + '----------------------------------------------------------------------------------------------- + + Dim fechaActualUTC As Date = DateTime.UtcNow + + Dim msjInvalidezCert As String = "" + + ' Raíz FNMT: + + If Not UtilsCert.ValidoCertSinRevoc(raizFnmtBin, raizFnmtBin, fechaActualUTC, msjInvalidezCert) Then + Throw New Exception("La Raíz de la FNMT NO es válida." & vbCrLf & msjInvalidezCert) + End If + + If UtilsCert.CalcularSHA1Str(raizFnmtBin) <> "EC503507B215C4956219E2A89A5B42992C4C2C20" Then + Throw New Exception("Problema de seguridad encontrado.") + End If + + ' AC FNMT Usuarios: + + If Not UtilsCert.ValidoCertSinRevoc(acFnmtUsuariosBin, raizFnmtBin, fechaActualUTC, msjInvalidezCert) Then + Throw New Exception("La AC FNMT Usuarios NO es válida." & vbCrLf & msjInvalidezCert) + End If + + Dim infoRevACUsuarios As UtilsCert.InfoRevoc = UtilsCert.ConsultarInfoRevoc(acFnmtUsuariosBin, raizFnmtBin) + + If infoRevACUsuarios.EstadoRevoc <> UtilsCert.EstadoRevocEnum.NoRevocado Then + Throw New Exception("No se puede confiar en la AC FNMT Usuarios." & vbCrLf & + "Estado de revocación: " & UtilsCert.EstadoRevocToString(infoRevACUsuarios.EstadoRevoc) & vbCrLf & + infoRevACUsuarios.MsjEstadoRevoc) + End If + + ' AC FNMT Representación: + + If Not UtilsCert.ValidoCertSinRevoc(acFnmtRepresentacionBin, raizFnmtBin, fechaActualUTC, msjInvalidezCert) Then + Throw New Exception("La AC FNMT Representación NO es válida." & vbCrLf & msjInvalidezCert) + End If + + Dim infoRevACRepresentacion As UtilsCert.InfoRevoc = UtilsCert.ConsultarInfoRevoc(acFnmtRepresentacionBin, raizFnmtBin) + + If infoRevACRepresentacion.EstadoRevoc <> UtilsCert.EstadoRevocEnum.NoRevocado Then + Throw New Exception("No se puede confiar en la AC FNMT Representación." & vbCrLf & + "Estado de revocación: " & UtilsCert.EstadoRevocToString(infoRevACRepresentacion.EstadoRevoc) & vbCrLf & + infoRevACRepresentacion.MsjEstadoRevoc) + End If + + End Sub + Public Shared Function ListadoDIFirmantes(arrayCertsPadresBin As Byte()(), pdf As String, Optional IndicesCertPadresRepresentacion As List(Of Integer) = Nothing, Optional CompruebaTodasLasFirmas As Boolean = False, Optional CompruebaCoberturaFirma As Boolean = False, Optional ByRef Advertencias As List(Of tsPDFUtilException) = Nothing) As List(Of String) + Try + Dim ms As MemoryStream = New MemoryStream(System.IO.File.ReadAllBytes(pdf)) + Return ListadoDIFirmantes(arrayCertsPadresBin, ms, IndicesCertPadresRepresentacion, CompruebaTodasLasFirmas, CompruebaCoberturaFirma, Advertencias) + Catch ex As Exception + Throw New Exception("Error comprobando firma del fichero " & pdf & ". " & ex.Message, ex) + End Try + End Function + + Public Shared Function ListadoDIFirmantes(arrayCertsPadresBin As Byte()(), pdf As Stream, Optional IndicesCertPadresRepresentacion As List(Of Integer) = Nothing, Optional CompruebaTodasLasFirmas As Boolean = False, Optional CompruebaCoberturaFirma As Boolean = False, Optional ByRef Advertencias As List(Of tsPDFUtilException) = Nothing) As List(Of String) + Dim reader As PdfReader = Nothing + Dim ListaFirmasCorrectas As New List(Of String) + Try + Dim bRespuestaOCSP() As Byte = Nothing + reader = New PdfReader(pdf) + Dim pdfd = New PdfDocument(reader) + Dim su = New SignatureUtil(pdfd) + Dim nombres = su.GetSignatureNames + If nombres.Count = 0 Then Throw New Exception("El documento no contiene ninguna firma digital") + Dim CifComprobado As Boolean = False + Dim Nifencontrado As Boolean = False + For Each n In nombres + Dim dni As String = "" + Try + dni = "" + Dim pk = su.ReadSignatureData(n) + Dim pc = pk.GetSigningCertificate + Dim certbin = New X509Certificate2(pc.GetEncoded).Export(X509ContentType.Cert) + Dim iPadre As Integer = UtilsCert.BuscarCertPadre(certbin, arrayCertsPadresBin) + + If IndicesCertPadresRepresentacion IsNot Nothing AndAlso IndicesCertPadresRepresentacion.Contains(iPadre) Then + Try + dni = UtilsCert.ObtenerValorAtributoDN(certbin, "2.5.4.97") + If dni.StartsWith("VATES-") Then dni = dni.Split("-"c)(1) + Catch + End Try + Else + Try + dni = UtilsCert.ObtenerValorAtributoDN(certbin, "2.5.4.5") + If dni.StartsWith("IDCES-") Then dni = dni.Split("-"c)(1) + Catch + End Try + End If + If Not pk.VerifySignatureIntegrityAndAuthenticity Then Throw New tsPDFUtilException("La firma es incorrecta o el documento fue modificado", "FIRMA_INCORRECTA") + If CompruebaCoberturaFirma AndAlso Not su.SignatureCoversWholeDocument(n) Then Throw New tsPDFUtilException("Aunque la firma es correcta, el documento fue modificado después de alguna firma", "DOCUMENTO_MODIFICADO") + If iPadre < 0 Then Throw New tsPDFUtilException("Certificado no admitido", "CERTIFICADO_NO_ADMITIDO") + Dim msjInvalidezCert As String = "" + Dim validoCert As Boolean = UtilsCert.ValidoCertSinRevoc(certbin, arrayCertsPadresBin, DateTime.UtcNow, msjInvalidezCert) + If Not validoCert Then Throw New Exception("Certificado Inválido: " & msjInvalidezCert) + Dim infoRev As UtilsCert.InfoRevoc = UtilsCert.ConsultarInfoRevoc(certbin, arrayCertsPadresBin) + If infoRev.RespuestaEfectiva AndAlso infoRev.RespuestaOCSPBin IsNot Nothing Then + bRespuestaOCSP = infoRev.RespuestaOCSPBin + End If + If infoRev.EstadoRevoc <> UtilsCert.EstadoRevocEnum.NoRevocado Then + Throw New tsPDFUtilException("No se puede confiar en el certificado. Estado de revocación: " & UtilsCert.EstadoRevocToString(infoRev.EstadoRevoc), "CERTIFICADO_REVOCADO") + End If + ListaFirmasCorrectas.Add(dni) + Catch ex As tsPDFUtilException + If Advertencias IsNot Nothing Then + Advertencias.Add(ex) + End If + Catch ex As Exception + If Advertencias IsNot Nothing Then + Advertencias.Add(New tsPDFUtilException(ex.Message, "DESCONOCIDA")) + End If + End Try + Next + Return ListaFirmasCorrectas + Catch ex As Exception + Throw New Exception(ex.Message, ex) + Finally + If reader IsNot Nothing Then + reader.Close() + End If + End Try + End Function + + Public Shared Function ListadoCamposFirmas(pdf As String) As List(Of String) + Try + Dim ms As MemoryStream = New MemoryStream(System.IO.File.ReadAllBytes(pdf)) + Return ListadoCamposFirmas(ms) + Catch ex As Exception + Throw New Exception("Error comprobando nombres de campos de firmas " & pdf & ". " & ex.Message, ex) + End Try + End Function + Public Shared Function ListadoCamposFirmas(pdf As Stream) As List(Of String) + Dim reader As PdfReader = Nothing + Try + reader = New PdfReader(pdf) + Dim pdfd = New PdfDocument(reader) + Dim su = New SignatureUtil(pdfd) + Dim nombres = su.GetSignatureNames.ToList + Return nombres + Catch ex As Exception + Throw New Exception(ex.Message, ex) + Finally + If reader IsNot Nothing Then + reader.Close() + End If + End Try + End Function + Public Shared Function ListadoCampos(pdf As String) As List(Of String) + Try + Dim ms As MemoryStream = New MemoryStream(System.IO.File.ReadAllBytes(pdf)) + Return ListadoCampos(ms) + Catch ex As Exception + Throw New Exception("Error comprobando nombres de campos " & pdf & ". " & ex.Message, ex) + End Try + End Function + Public Shared Function ListadoCampos(pdf As Stream) As List(Of String) + Dim reader As PdfReader = Nothing + Try + reader = New PdfReader(pdf) + Dim pdfd = New PdfDocument(reader) + Dim campos = PdfAcroForm.GetAcroForm(pdfd, False).GetAllFormFields.Select(Function(x) x.Key).ToList + Return campos + Catch ex As Exception + Throw New Exception("Error comprobando nombres de campos. " & ex.Message, ex) + Finally + If reader IsNot Nothing Then + reader.Close() + End If + End Try + End Function +End Class diff --git a/TSPdfUtils/My Project/Application.myapp b/TSPdfUtils/My Project/Application.myapp new file mode 100644 index 0000000..c0f7fef --- /dev/null +++ b/TSPdfUtils/My Project/Application.myapp @@ -0,0 +1,10 @@ + + + true + Form1 + false + 0 + true + 0 + true + \ No newline at end of file diff --git a/TSPdfUtils/My Project/Resources.Designer.vb b/TSPdfUtils/My Project/Resources.Designer.vb new file mode 100644 index 0000000..9864bd1 --- /dev/null +++ b/TSPdfUtils/My Project/Resources.Designer.vb @@ -0,0 +1,83 @@ +'------------------------------------------------------------------------------ +' +' Este código fue generado por una herramienta. +' Versión de runtime:4.0.30319.42000 +' +' Los cambios en este archivo podrían causar un comportamiento incorrecto y se perderán si +' se vuelve a generar el código. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + +Imports System + +Namespace My.Resources + + 'StronglyTypedResourceBuilder generó automáticamente esta clase + 'a través de una herramienta como ResGen o Visual Studio. + 'Para agregar o quitar un miembro, edite el archivo .ResX y, a continuación, vuelva a ejecutar ResGen + 'con la opción /str o recompile su proyecto de VS. + ''' + ''' Clase de recurso fuertemente tipado, para buscar cadenas traducidas, etc. + ''' + _ + Friend Module Resources + + Private resourceMan As Global.System.Resources.ResourceManager + + Private resourceCulture As Global.System.Globalization.CultureInfo + + ''' + ''' Devuelve la instancia de ResourceManager almacenada en caché utilizada por esta clase. + ''' + _ + Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager + Get + If Object.ReferenceEquals(resourceMan, Nothing) Then + Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("TSpdfUtils.Resources", GetType(Resources).Assembly) + resourceMan = temp + End If + Return resourceMan + End Get + End Property + + ''' + ''' Reemplaza la propiedad CurrentUICulture del subproceso actual para todas las + ''' búsquedas de recursos mediante esta clase de recurso fuertemente tipado. + ''' + _ + Friend Property Culture() As Global.System.Globalization.CultureInfo + Get + Return resourceCulture + End Get + Set + resourceCulture = value + End Set + End Property + + ''' + ''' Busca un recurso adaptado de tipo System.Byte[]. + ''' + Friend ReadOnly Property Fogra39L() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Fogra39L", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' Busca un recurso adaptado de tipo System.Byte[]. + ''' + Friend ReadOnly Property sRGB2014() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("sRGB2014", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + End Module +End Namespace diff --git a/TSPdfUtils/My Project/Resources.resx b/TSPdfUtils/My Project/Resources.resx new file mode 100644 index 0000000..09c3478 --- /dev/null +++ b/TSPdfUtils/My Project/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\Coated_Fogra39L_VIGC_300.icc;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\sRGB2014.icc;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TSPdfUtils/Resource.Designer.vb b/TSPdfUtils/Resource.Designer.vb new file mode 100644 index 0000000..841bbf0 Binary files /dev/null and b/TSPdfUtils/Resource.Designer.vb differ diff --git a/TSPdfUtils/Resource.resx b/TSPdfUtils/Resource.resx new file mode 100644 index 0000000..ea321c1 --- /dev/null +++ b/TSPdfUtils/Resource.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Resources\ilits.ttf;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + \ No newline at end of file diff --git a/TSPdfUtils/Resources/Coated_Fogra39L_VIGC_300.icc b/TSPdfUtils/Resources/Coated_Fogra39L_VIGC_300.icc new file mode 100644 index 0000000..8cba868 Binary files /dev/null and b/TSPdfUtils/Resources/Coated_Fogra39L_VIGC_300.icc differ diff --git a/TSPdfUtils/Resources/ilits.ttf b/TSPdfUtils/Resources/ilits.ttf new file mode 100644 index 0000000..00a4e0c Binary files /dev/null and b/TSPdfUtils/Resources/ilits.ttf differ diff --git a/TSPdfUtils/Resources/sRGB2014.icc b/TSPdfUtils/Resources/sRGB2014.icc new file mode 100644 index 0000000..49afbfe Binary files /dev/null and b/TSPdfUtils/Resources/sRGB2014.icc differ diff --git a/TSPdfUtils/Sellado.cs b/TSPdfUtils/Sellado.cs new file mode 100644 index 0000000..87c3ba8 --- /dev/null +++ b/TSPdfUtils/Sellado.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.IO; + +namespace TSpdfUtils +{ + + public class Sellado + { + public static void SellaPDF(Stream PdfOrigen, Stream PdfDestino, List TextosAInsertar, List ImagenesAInsertar) + { + + } + + + public class TextoEnPdf + { + public string Texto { get; set; } + public FuenteEnum Fuente { get; set; } + public float TamañoFuente { get; set; } + public System.Drawing.Color Color { get; set; } + public double Transparencia { get; set; } + public EsquinaEnum EsquinaReferencia { get; set; } + public float AnguloRotacion { get; set; } + public AlineamientoEnum Alineamiento { get; set; } + public float CoordenadaX { get; set; } + public float CoordenadaY { get; set; } + public int PaginaInicio { get; set; } + public int PaginaFin { get; set; } + } + + public class ImagenEnPdf + { + public byte[] Imagen { get; set; } + public EsquinaEnum EsquinaReferencia { get; set; } + public double Transparencia { get; set; } + public float CoordenadaX { get; set; } + public float CoordenadaY { get; set; } + public float AnguloRotacion { get; set; } + public int PaginaInicio { get; set; } + public int PaginaFin { get; set; } + } + public enum EsquinaEnum : int + { + INFERIOR_IZQUIERDA = 0, + INFERIOR_DERECHA = 1, + SUPERIOR_IZQUIERDA = 2, + SUPERIOR_DERECHA = 3 + } + + public enum AlineamientoEnum : int + { + IZQUIERDA = 0, + DERECHA = 1, + CENTRO = 2, + JUSTIFICADO = 3 + } + public enum FuenteEnum + { + COURIER, + COURIER_BOLD, + COURIER_BOLDOBLIQUE, + COURIER_OBLIQUE, + HELVETICA, + HELVETICA_BOLD, + HELVETICA_BOLDOBLIQUE, + HELVETICA_OBLIQUE, + SYMBOL, + TIMES_ROMAN, + TIMES_BOLD, + TIMES_BOLDITALIC, + TIMES_ITALIC, + ZAPFDINGBATS + } + } +} \ No newline at end of file diff --git a/TSPdfUtils/TSpdfUtils.vbproj b/TSPdfUtils/TSpdfUtils.vbproj new file mode 100644 index 0000000..5087e07 --- /dev/null +++ b/TSPdfUtils/TSpdfUtils.vbproj @@ -0,0 +1,83 @@ + + + SAK + SAK + SAK + SAK + + + True + False + $(TargetDir)bin\$(Configuration)\$(TargetFramework)\TSpdfUtils.xml + 4 + + + netstandard2.0;net48 + library + false + TSpdfUtils + TSpdfUtils + TSpdfUtils + libreria, netstandard2.0 + 1.0.18 + 1.0.18 + 1.0.18 + Manuel + Tecnosis S.A + Utilidades de tratamiento de pdf, de firmas digitales e imagenes. + + - 1.0.18 2026-05-20 Correcciones dependencias Microsoft.Extensions.Logging + - 1.0.16 2025-10-23 Actualización de tspdfutilscore + - 1.0.14 2025-10-09 Limpieza de compilados + - 1.0.13 2025-10-09 Se eliminan destinos del framework 461 y 48 + - 1.0.12 2025-10-09 Actualización para paquete baget + - 1.0.11 2025-10-06 Corrección en dependencias en tspPDFUtilsCore + - 1.0.10 2025-10-06 Corrección en dependencias + - 1.0.9 2025-10-03 Corrección en dependencias + - 1.0.8 Corrección en dependencias + - 1.0.7 Corrección en TSpdf.io + - 1.0.6 Corrección en TSpdf.io + - 1.0.4 Corrección en TSpdf.io + + + + 1701;1702;1591;1570;1572;1573;1574;1580;1584;1658 + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + My.Resources + VbMyResourcesResXFileCodeGenerator + Resources.Designer.vb + + + \ No newline at end of file diff --git a/TSPdfUtils/Utilidades.vb b/TSPdfUtils/Utilidades.vb new file mode 100644 index 0000000..4002523 --- /dev/null +++ b/TSPdfUtils/Utilidades.vb @@ -0,0 +1,195 @@ +Imports System.IO +Imports System.Text +Imports TSpdf.Kernel.Pdf +Imports TSpdf.Kernel.Utils +Imports TSpdf.Pdfa + +Public Class Utilidades + 'Convierte un pdf normal en el formato pdf_A_3B + Public Shared Function crearPDFA(ByVal pdfOrigen As Stream) As MemoryStream + + Dim reader As New PdfReader(pdfOrigen) + Dim ms As New MemoryStream + + Dim writerProps As New WriterProperties() + writerProps.SetPdfVersion(PdfVersion.PDF_2_0) + + Dim writer As New PdfWriter(ms, writerProps) + writer.SetSmartMode(True) + ' Necesario para que el memorystream no se cierre y siga abieto + writer.SetCloseStream(False) + + Dim sourcePdf As New PdfDocument(reader) + + Dim esRGB As Boolean = recorrerPaginas(sourcePdf) + + For page As Integer = 1 To sourcePdf.GetNumberOfPages() + + Dim paginaActual = sourcePdf.GetPage(page) + Dim anotaciones = paginaActual.GetAnnotations() + + If anotaciones IsNot Nothing Then + + For Each anotacionActual In anotaciones + Dim diccionario = anotacionActual.GetPdfObject() + + If Not diccionario.ContainsKey(PdfName.F) Then + diccionario.Put(PdfName.F, New PdfNumber(4)) + End If + Next + End If + + + Next + + Dim disPDF As PdfADocument + + 'If esRGB Then + + ' disPDF = New PdfADocument(writer, PdfAConformanceLevel.PDF_A_3B, + ' New PdfOutputIntent("Custom", "", "https://www.color.org", "sRGB", + ' New MemoryStream(My.Resources.sRGB2014))) + + 'Else + ' disPDF = New PdfADocument(writer, PdfAConformanceLevel.PDF_A_3B, + ' New PdfOutputIntent("Custom", "", "https://www.color.org", "FOGRA39", + ' New MemoryStream(My.Resources.Fogra39L))) + 'End If + + disPDF = New PdfADocument(writer, PdfAConformanceLevel.PDF_A_3B, + New PdfOutputIntent("Custom", "", "https://www.color.org", "sRGB", + New MemoryStream(My.Resources.sRGB2014))) + + + + disPDF.InitializeOutlines() + ' Configurar parámetros requeridos + disPDF.SetTagged() + disPDF.GetCatalog().SetLang(New PdfString("es-ES")) + disPDF.GetCatalog().SetViewerPreferences(New PdfViewerPreferences().SetDisplayDocTitle(True)) + + Dim merger As New PdfMerger(disPDF, True, True) + merger.Merge(sourcePdf, 1, sourcePdf.GetNumberOfPages()) + + sourcePdf.Close() + disPDF.Close() + reader.Close() + writer.Close() + + ' Esto hace falta para volver al inicio del memorystream + ms.Position = 0 + + Return ms + End Function + Public Shared Function controlarInterpolate(ByVal resources As PdfDictionary, ByVal tieneRGB As Boolean) As Boolean + + + Dim xObjects = resources?.GetAsDictionary(PdfName.XObject) + Dim tieneRGBActual As Boolean = tieneRGB + + If xObjects IsNot Nothing Then + For Each key In xObjects.KeySet() + Dim stream = xObjects.GetAsStream(key) + Dim subtype = stream.GetAsName(PdfName.Subtype) + + If PdfName.Image.Equals(subtype) Then + + If stream.GetAsBoolean(PdfName.Interpolate)?.GetValue = True Then + + stream.Put(PdfName.Interpolate, PdfBoolean.FALSE) + End If + + Dim colorSpace = stream.Get(PdfName.ColorSpace) + + If colorSpace IsNot Nothing AndAlso colorSpace.Equals(PdfName.DeviceRGB) Then + tieneRGBActual = True + tieneRGB = True + End If + + ElseIf PdfName.Form.Equals(subtype) Then + + Dim formRes = stream.GetAsDictionary(PdfName.Resources) + + If formRes IsNot Nothing Then + Dim hijoTieneRGB As Boolean = controlarInterpolate(formRes, tieneRGBActual) + + If hijoTieneRGB Then + tieneRGBActual = True + End If + End If + + End If + Next + End If + + Return tieneRGBActual + End Function + + + + Public Shared Function recorrerPaginas(ByVal pdf As PdfDocument) As Boolean + Dim tieneRGB As Boolean = False + + For i As Integer = 1 To pdf.GetNumberOfPages + Dim rgbMinimo As Boolean = False + + Dim diccPaginaActual = pdf.GetPage(i).GetResources().GetPdfObject() + rgbMinimo = controlarInterpolate(diccPaginaActual, tieneRGB) + + If rgbMinimo Then + tieneRGB = True + End If + Next + + Return tieneRGB + End Function + Public Shared Sub RepararAnotacionesSinF(pdfDoc As PdfDocument) + + For page As Integer = 1 To pdfDoc.GetNumberOfPages() + + Dim paginaActual = pdfDoc.GetPage(page) + Dim anotaciones = paginaActual.GetAnnotations() + + If anotaciones IsNot Nothing Then + For Each anotacionActual In anotaciones + Dim diccionario = anotacionActual.GetPdfObject() + + If Not diccionario.ContainsKey(PdfName.F) Then + diccionario.Put(PdfName.F, New PdfNumber(4)) + End If + Next + End If + + Next + + End Sub + + + Public Shared Function TieneMarcaPdfA(pdfStream As Stream) As Boolean + Try + If pdfStream Is Nothing Then Return False + + If pdfStream.CanSeek Then + pdfStream.Position = 0 + End If + + Using reader As New PdfReader(pdfStream) + Using pdf As New PdfDocument(reader) + + Dim metadata As Byte() = pdf.GetXmpMetadata() + + If metadata Is Nothing OrElse metadata.Length = 0 Then + Return False + End If + + Dim xmpString As String = Encoding.UTF8.GetString(metadata) + + Return xmpString.Contains("pdfaid:part") + End Using + End Using + + Catch + Return False + End Try + End Function +End Class diff --git a/TSPdfUtils/UtilsCert.vb b/TSPdfUtils/UtilsCert.vb new file mode 100644 index 0000000..9a7b40e --- /dev/null +++ b/TSPdfUtils/UtilsCert.vb @@ -0,0 +1,2375 @@ +'--------------------------------------------------------------------------- +' UtilsCert: Utilidades relacionadas con la Certificación: +' validación, +' consulta de revocación mediante OCSP (RFC 2560, vía HTTP), +' importación de certificados desde Almacenes de Windows, +' etc. +' +' Se requiere la librería BouncyCastle. +'--------------------------------------------------------------------------- +' Versión: 1.0 +' Fecha: 26-2-2019 +' +' Todos los miembros de esta clase son compartidos (Shared). +' No se permite crear instancias de esta clase. +' +' TECNOSIS S.A. +'--------------------------------------------------------------------------- + +Imports System.IO +Imports SSC = System.Security.Cryptography +Imports BC = Org.BouncyCastle +Imports Org.BouncyCastle.Asn1 + +Public Class UtilsCert + + + Public Shared Sub ValidarAutoridadesFNMT(ByVal raizFnmtBin As Byte(), + ByVal acFnmtUsuariosBin As Byte(), + ByVal acFnmtRepresentacionBin As Byte()) + + '----------------------------------------------------------------------------------------------- + ' Valida las Autoridades de Certificación de la FNMT permitidas (PASO 1 DEL PROCESO GENERAL). + ' La validación se realiza en la FECHA ACTUAL. + ' Eleva EXCEPCIÓN si hay algún problema. + '----------------------------------------------------------------------------------------------- + ' 1. Validar las Autoridades de Certificación (ACs) en las que confiamos: + ' 1.1 Validar las Raíces con ellas mismas (sin analizar revocación), y + ' opcionalmente comprobar que sus hashes SHA-1 coinciden con los precableados + ' (para evitar que las Raíces sean sustituidas en la Base de Datos). + ' 1.2 Validar las ACs intermedias con sus padres correspondientes, y comprobar su revocación. + '----------------------------------------------------------------------------------------------- + + Dim fechaActualUTC As Date = DateTime.UtcNow + + Dim msjInvalidezCert As String = "" + + ' Raíz FNMT: + + If Not UtilsCert.ValidoCertSinRevoc(raizFnmtBin, raizFnmtBin, fechaActualUTC, msjInvalidezCert) Then + Throw New Exception("La Raíz de la FNMT NO es válida." & vbCrLf & msjInvalidezCert) + End If + + If UtilsCert.CalcularSHA1Str(raizFnmtBin) <> "EC503507B215C4956219E2A89A5B42992C4C2C20" Then + Throw New Exception("Problema de seguridad encontrado.") + End If + + ' AC FNMT Usuarios: + + If Not UtilsCert.ValidoCertSinRevoc(acFnmtUsuariosBin, raizFnmtBin, fechaActualUTC, msjInvalidezCert) Then + Throw New Exception("La AC FNMT Usuarios NO es válida." & vbCrLf & msjInvalidezCert) + End If + + Dim infoRevACUsuarios As UtilsCert.InfoRevoc = UtilsCert.ConsultarInfoRevoc(acFnmtUsuariosBin, raizFnmtBin) + + If infoRevACUsuarios.EstadoRevoc <> UtilsCert.EstadoRevocEnum.NoRevocado Then + Throw New Exception("No se puede confiar en la AC FNMT Usuarios." & vbCrLf & + "Estado de revocación: " & UtilsCert.EstadoRevocToString(infoRevACUsuarios.EstadoRevoc) & vbCrLf & + infoRevACUsuarios.MsjEstadoRevoc) + End If + + ' AC FNMT Representación: + + If Not UtilsCert.ValidoCertSinRevoc(acFnmtRepresentacionBin, raizFnmtBin, fechaActualUTC, msjInvalidezCert) Then + Throw New Exception("La AC FNMT Representación NO es válida." & vbCrLf & msjInvalidezCert) + End If + + Dim infoRevACRepresentacion As UtilsCert.InfoRevoc = UtilsCert.ConsultarInfoRevoc(acFnmtRepresentacionBin, raizFnmtBin) + + If infoRevACRepresentacion.EstadoRevoc <> UtilsCert.EstadoRevocEnum.NoRevocado Then + Throw New Exception("No se puede confiar en la AC FNMT Representación." & vbCrLf & + "Estado de revocación: " & UtilsCert.EstadoRevocToString(infoRevACRepresentacion.EstadoRevoc) & vbCrLf & + infoRevACRepresentacion.MsjEstadoRevoc) + End If + + End Sub + + ' Ocultamos el constructor para evitar que se puedan crear instancias de esta clase: + Private Sub New() + + End Sub + + '------------------------------------------------- + + Public Enum EstadoRevocEnum As Integer + + '-------------------------------------- + ' Estado de revocación del certificado + '-------------------------------------- + + ' Se muestran los valores correspondientes del campo MsjEstadoRevoc del objeto InfoRevoc: + + NoRevocado = 0 ' MsjEstadoRevoc = Nothing + Revocado = 1 ' MsjEstadoRevoc = " (CodRev=)" o "Causa no disponible (CodRev=-1)" + + Indeterminado = 2 ' MsjEstadoRevoc = "" + + End Enum + + Public Class InfoRevoc + + Public CertBin As Byte() ' Certificado en ASN.1 DER (.cer) objeto de la consulta OCSP. + + Public EstadoRevoc As EstadoRevocEnum + Public MsjEstadoRevoc As String ' Su contenido depende del valor de EstadoRevoc (ver EstadoRevocEnum). + Public FechaRevocUTC As Date ' Fecha de la revocación si EstadoRevoc = Revocado; Nothing en otro caso. + + Public ThisUpdateUTC As Date ' Valor ThisUpdate de la respuesta OCSP, o Nothing si no está presente. + Public NextUpdateUTC As Date ' Valor NextUpdate de la respuesta OCSP, o Nothing si no está presente. + + Public RespuestaOCSPBin As Byte() ' Respuesta OCSP en binario, o Nothing si no se ha recibido. + Public RespuestaEfectiva As Boolean ' True si RespuestaOCSPBin es una respuesta 'efectiva' (responseStatus = successful). + Public UrlResponder As String ' URL del Responder OCSP. + Public ResponderBin As Byte() ' Certificado Responder en ASN.1 DER (.cer) usado para validar la firma de la respuesta OCSP; Nothing si no presente. + + '--------------------- + + Public Overrides Function ToString() As String + + Dim res As String + Try + + If CertBin Is Nothing Then + Throw New Exception("'CertBin' es Nothing.") + End If + + Dim cert As New SSC.X509Certificates.X509Certificate2(CertBin) + + res = "INFORMACIÓN DEL CERTIFICADO:" & vbCrLf & + " Titular: " & cert.Subject & vbCrLf & + " Emisor: " & cert.Issuer & vbCrLf & + " Certificado válido desde: " & DateToString(cert.NotBefore) & vbCrLf & + " Certificado válido hasta: " & DateToString(cert.NotAfter) & vbCrLf & + "INFORMACIÓN DE REVOCACIÓN:" & vbCrLf & + " Estado: " & EstadoRevocToString(EstadoRevoc) & vbCrLf + + If EstadoRevoc <> EstadoRevocEnum.NoRevocado Then + If EstadoRevoc = EstadoRevocEnum.Revocado Then + res &= " Causa de revocación: " & MsjEstadoRevoc & vbCrLf & + " Fecha de revocación: " & DateToString(FechaRevocUTC.ToLocalTime()) & vbCrLf + Else + res &= " Mensaje:" & If(MsjEstadoRevoc = "", " ", vbCrLf & Indent(MsjEstadoRevoc, 8)) & vbCrLf + End If + End If + + res &= " Información de revocación válida desde: " & If(ThisUpdateUTC = Nothing, "", DateToString(ThisUpdateUTC.ToLocalTime())) & vbCrLf & + " Información de revocación válida hasta: " & If(NextUpdateUTC = Nothing, "", DateToString(NextUpdateUTC.ToLocalTime())) & vbCrLf & + " Respuesta OCSP efectiva: " & If(RespuestaEfectiva, "Sí", "No") & vbCrLf & + " URL del servidor OCSP: " & UrlResponder & vbCrLf + + If ResponderBin Is Nothing Then + res &= " Certificado 'Responder': " + Else + Dim responder As New SSC.X509Certificates.X509Certificate2(ResponderBin) + res &= " Certificado 'Responder': " & responder.Subject + End If + + Catch ex As Exception + Throw New Exception("Error ejecutando ToString de InfoRevoc." & vbCrLf & ex.Message, ex) + End Try + + Return res + + End Function + + End Class + + Public Class CamposRespuestaOCSP + + Public responseStatus As Integer = -1 + + Public signatureAlgorithmOID As String = Nothing + Public signatureAlgorithmName As String = Nothing + Public numCertsResponder As Integer = 0 + Public authorizedResponderBin As Byte() = Nothing + Public producedAtUTC As Date = Nothing + Public numResponseExtensions As Integer = 0 + Public numResponses As Integer = 0 + + Public certSerialNumber As Byte() = Nothing + Public certStatus As EstadoRevocEnum = EstadoRevocEnum.Indeterminado + Public revocationTimeUTC As Date = Nothing + Public revocationReason As Integer = -1 + Public thisUpdateUTC As Date = Nothing + Public nextUpdateUTC As Date = Nothing + Public numSingleExtensions As Integer = 0 + + End Class + + '------------------------------------------------- + +#Region "Métodos públicos compartidos (Shared)" + + Public Shared Function ConsultarInfoRevoc(ByVal certBin As Byte(), + ByVal certPadreBin As Byte(), + Optional ByVal usarUrlResponderDeCert As Boolean = True, + Optional ByVal urlResponder As String = Nothing, + Optional ByVal arrayPadresResponderAutBin As Byte()() = Nothing, + Optional ByVal usarResponderDeRespuestaOCSP As Boolean = True, + Optional ByVal arrayRespondersBin As Byte()() = Nothing, + Optional ByVal timeoutIntentoSegundos As Integer = 15, + Optional ByVal numIntentosMax As Integer = 4, + Optional ByVal esperaEntreIntentosMiliseg As Integer = 3000, + Optional ByVal requestorBin As Byte() = Nothing, + Optional ByVal chainRequestorBin As Byte()() = Nothing, + Optional ByVal privadaRequestor As SSC.RSAParameters = Nothing) As InfoRevoc + + '----------------------------------------------------------------------------------------------------------- + ' Los certificados están en ASN.1 DER (.cer). + '----------------------------------------------------------------------------------------------------------- + ' certBin: Certificado objeto de la consulta OCSP. + ' + ' certPadreBin: Certificado padre de certBin. Se asume que ha sido VALIDADO PREVIAMENTE. + ' + ' usarUrlResponderDeCert: True si se desea usar la URL del Responder OCSP contenida en certBin + ' (en la extensión AuthorityInfoAccess). + ' + ' urlResponder: URL del Responder OCSP que se usará en el caso de que certBin no la contenga, + ' o cuando usarUrlResponderDeCert = False. + ' Si certBin contiene la URL del Responder y usarUrlResponderDeCert = True, + ' entonces NO se usará la URL pasada como argumento. + ' + ' arrayPadresResponderAutBin: Padres permitidos para el Responder Autorizado. + ' Se usarán para validar el Responder Autorizado contenido en la respuesta OCSP. + ' Se asume que los padres han sido VALIDADOS PREVIAMENTE. + ' Nothing equivale al array {certPadreBin} (siguiendo RFC 2560). + ' + ' usarResponderDeRespuestaOCSP: True si se desea usar el Responder Autorizado contenido en la respuesta OCSP + ' (en el campo 'certs' del Sequence ASN.1 'BasicOCSPResponse'). + ' Responder Autorizado: Certificado con el OID OCSPSigning en la extensión + ' ExtendedKeyUsage. + ' + ' arrayRespondersBin: Responders locales (se asume que han sido VALIDADOS PREVIAMENTE) que se usarán para + ' validar la firma de la respuesta OCSP en el caso de que ésta no contenga un Responder + ' Autorizado, o cuando usarResponderDeRespuestaOCSP = False. + ' Si la respuesta contiene un Responder Autorizado y usarResponderDeRespuestaOCSP = True, + ' entonces NO se usarán los Responders locales pasados como argumento. + ' Nothing equivale al array {certPadreBin} (siguiendo RFC 2560). + ' + ' timeoutIntentoSegundos: Timeout para cada "intento" (consulta OCSP) (en segundos). + ' + ' numIntentosMax: Número de intentos máximo. + ' + ' esperaEntreIntentosMiliseg: Espera entre intentos (en milisegundos). + ' + ' requestorBin: Certificado Requestor. Si está presente, entonces se firmará la petición OCSP. + ' + ' chainRequestorBin: Cadena de certificación del Requestor que se enviará en la petición OCSP + ' (en el campo opcional 'certs' del Sequence ASN.1 'Signature'). + ' Debe tener el ORDEN CORRECTO. + ' Es OPCIONAL. + ' + ' privadaRequestor: Clave privada RSA con la que se firmará la petición OCSP. + ' Necesaria si requestorBin está presente. + '----------------------------------------------------------------------------------------------------------- + + Dim infoRev As InfoRevoc + Try + + If numIntentosMax < 1 Then Throw New Exception("'numIntentosMax' es menor que 1: " & numIntentosMax) + If numIntentosMax > 10 Then Throw New Exception("'numIntentosMax' es mayor que 10: " & numIntentosMax) + + If esperaEntreIntentosMiliseg < 0 Then Throw New Exception("'esperaEntreIntentosMiliseg' es menor que 0: " & esperaEntreIntentosMiliseg) + + '--------------- + + Dim reintentable As Boolean + + Dim intento As Integer + For intento = 1 To numIntentosMax + + ConsultarOCSP(certBin, certPadreBin, + usarUrlResponderDeCert, urlResponder, + arrayPadresResponderAutBin, usarResponderDeRespuestaOCSP, arrayRespondersBin, + timeoutIntentoSegundos, + requestorBin, chainRequestorBin, privadaRequestor, + reintentable, infoRev) + + If reintentable AndAlso (intento < numIntentosMax) Then + System.Threading.Thread.Sleep(esperaEntreIntentosMiliseg) + Else + Exit For + End If + + Next + + Catch ex As Exception + Throw New Exception("Error ejecutando ConsultarInfoRevoc." & vbCrLf & ex.Message, ex) + End Try + + Return infoRev + + End Function + + Public Shared Function ConsultarInfoRevoc(ByVal certBin As Byte(), + ByVal arrayCertsPadresBin As Byte()(), + Optional ByVal arrayUsarUrlResponderDeCert As Boolean() = Nothing, + Optional ByVal arrayUrlsResponders As String() = Nothing, + Optional ByVal arrayArraysPadresResponderAutBin As Byte()()() = Nothing, + Optional ByVal arrayUsarResponderDeRespuestaOCSP As Boolean() = Nothing, + Optional ByVal arrayArraysRespondersBin As Byte()()() = Nothing, + Optional ByVal timeoutIntentoSegundos As Integer = 15, + Optional ByVal numIntentosMax As Integer = 4, + Optional ByVal esperaEntreIntentosMiliseg As Integer = 3000, + Optional ByVal arrayRequestorsBin As Byte()() = Nothing, + Optional ByVal arrayChainsRequestorsBin As Byte()()() = Nothing, + Optional ByVal arrayPrivadasRequestors As SSC.RSAParameters() = Nothing) As InfoRevoc + + '----------------------------------------------------------------------------------------------- + ' arrayCertsPadresBin: Array con los certificados padres permitidos para certBin. + ' Se asume que los certificados han sido VALIDADOS PREVIAMENTE. + ' La función busca el padre correspondiente. + ' Debe ser NO VACÍO. + '----------------------------------------------------------------------------------------------- + ' Los siguientes argumentos deben ser NOTHING o tener la MISMA LONGITUD que arrayCertsPadresBin: + ' + ' arrayUsarUrlResponderDeCert (Nothing equivale a un array con todo True) + ' arrayUrlsResponders + ' + ' arrayArraysPadresResponderAutBin + ' arrayUsarResponderDeRespuestaOCSP (Nothing equivale a un array con todo True) + ' arrayArraysRespondersBin + ' + ' arrayRequestorsBin + ' arrayChainsRequestorsBin + ' arrayPrivadasRequestors + ' + ' Cuando son distintos de Nothing, cada elemento i corresponde al padre i en arrayCertsPadresBin. + '----------------------------------------------------------------------------------------------- + + Dim certPadreBin As Byte() + + Dim usarUrlResponderDeCert As Boolean = True + Dim urlResponder As String = Nothing + + Dim arrayPadresResponderAutBin As Byte()() = Nothing + Dim usarResponderDeRespuestaOCSP As Boolean = True + Dim arrayRespondersBin As Byte()() = Nothing + + Dim requestorBin As Byte() = Nothing + Dim chainRequestorBin As Byte()() = Nothing + Dim privadaRequestor As SSC.RSAParameters = Nothing + + Try + + If certBin Is Nothing Then + Throw New Exception("'certBin' es Nothing.") + End If + + If (arrayCertsPadresBin Is Nothing) OrElse (arrayCertsPadresBin.Length = 0) Then + Throw New Exception("'arrayCertsPadresBin' es Nothing o vacío.") + End If + + + If (arrayUsarUrlResponderDeCert IsNot Nothing) AndAlso (arrayUsarUrlResponderDeCert.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayUsarUrlResponderDeCert' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + If (arrayUrlsResponders IsNot Nothing) AndAlso (arrayUrlsResponders.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayUrlsResponders' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + + If (arrayArraysPadresResponderAutBin IsNot Nothing) AndAlso (arrayArraysPadresResponderAutBin.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayArraysPadresResponderAutBin' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + If (arrayUsarResponderDeRespuestaOCSP IsNot Nothing) AndAlso (arrayUsarResponderDeRespuestaOCSP.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayUsarResponderDeRespuestaOCSP' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + If (arrayArraysRespondersBin IsNot Nothing) AndAlso (arrayArraysRespondersBin.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayArraysRespondersBin' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + + If (arrayRequestorsBin IsNot Nothing) AndAlso (arrayRequestorsBin.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayRequestorsBin' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + If (arrayChainsRequestorsBin IsNot Nothing) AndAlso (arrayChainsRequestorsBin.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayChainsRequestorsBin' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + If (arrayPrivadasRequestors IsNot Nothing) AndAlso (arrayPrivadasRequestors.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayPrivadasRequestors' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + '------------ + + Dim iPadre As Integer = BuscarCertPadre(certBin, arrayCertsPadresBin) + + If iPadre = -1 Then + Dim cert As New SSC.X509Certificates.X509Certificate2(certBin) + Throw New Exception("No se ha encontrado certificado emisor para el certificado de:" & vbCrLf & + " " & cert.Subject) + End If + + certPadreBin = arrayCertsPadresBin(iPadre) + + + If arrayUsarUrlResponderDeCert IsNot Nothing Then + usarUrlResponderDeCert = arrayUsarUrlResponderDeCert(iPadre) + End If + + If arrayUrlsResponders IsNot Nothing Then + urlResponder = arrayUrlsResponders(iPadre) + End If + + + If arrayArraysPadresResponderAutBin IsNot Nothing Then + arrayPadresResponderAutBin = arrayArraysPadresResponderAutBin(iPadre) + End If + + If arrayUsarResponderDeRespuestaOCSP IsNot Nothing Then + usarResponderDeRespuestaOCSP = arrayUsarResponderDeRespuestaOCSP(iPadre) + End If + + If arrayArraysRespondersBin IsNot Nothing Then + arrayRespondersBin = arrayArraysRespondersBin(iPadre) + End If + + + If arrayRequestorsBin IsNot Nothing Then + requestorBin = arrayRequestorsBin(iPadre) + End If + + If arrayChainsRequestorsBin IsNot Nothing Then + chainRequestorBin = arrayChainsRequestorsBin(iPadre) + End If + + If arrayPrivadasRequestors IsNot Nothing Then + privadaRequestor = arrayPrivadasRequestors(iPadre) + End If + + + Catch ex As Exception + Throw New Exception("Error ejecutando ConsultarInfoRevoc (sobrecarga para múltiples servidores OCSP)." & vbCrLf & ex.Message, ex) + End Try + + Return ConsultarInfoRevoc(certBin, certPadreBin, + usarUrlResponderDeCert, urlResponder, + arrayPadresResponderAutBin, usarResponderDeRespuestaOCSP, arrayRespondersBin, + timeoutIntentoSegundos, numIntentosMax, esperaEntreIntentosMiliseg, + requestorBin, chainRequestorBin, privadaRequestor) + + End Function + + + Public Shared Function FirmaValidaRespuestaOCSP(ByVal respuestaOCSPBin As Byte(), + ByVal fechaUTC As Date, + ByVal arrayPadresResponderAutBin As Byte()(), + Optional ByVal usarResponderDeRespuestaOCSP As Boolean = True, + Optional ByVal arrayRespondersBin As Byte()() = Nothing, + Optional ByRef responderBinUsado As Byte() = Nothing, + Optional ByRef msjInvalidez As String = Nothing) As Boolean + + '----------------------------------------------------------------------------------------------------------- + ' No se tiene en cuenta (ThisUpdate, NextUpdate) de la respuesta OCSP. + ' Los certificados están en ASN.1 DER (.cer). + '----------------------------------------------------------------------------------------------------------- + ' fechaUTC: Fecha en la que se validará el Responder Autorizado contenido en la respuesta OCSP. + ' + ' arrayPadresResponderAutBin: Padres permitidos para el Responder Autorizado. + ' Se usarán para validar el Responder Autorizado contenido en la respuesta OCSP. + ' Se asume que los padres han sido VALIDADOS PREVIAMENTE. + ' Debe ser no vacío si usarResponderDeRespuestaOCSP = True. + ' El valor Nothing NO tiene ningún significado especial. + ' + ' usarResponderDeRespuestaOCSP: True si se desea usar el Responder Autorizado contenido en la respuesta OCSP + ' (en el campo 'certs' del Sequence ASN.1 'BasicOCSPResponse'). + ' + ' arrayRespondersBin: Responders locales (se asume que han sido VALIDADOS PREVIAMENTE) que se usarán para + ' validar la firma de la respuesta OCSP en el caso de que ésta no contenga un Responder + ' Autorizado, o cuando usarResponderDeRespuestaOCSP = False. + ' Si la respuesta contiene un Responder Autorizado y usarResponderDeRespuestaOCSP = True, + ' entonces NO se usarán los Responders locales pasados como argumento. + ' El valor Nothing NO tiene ningún significado especial. + '----------------------------------------------------------------------------------------------------------- + ' responderBinUsado: Se devuelve el certificado Responder usado para validar la firma de la respuesta OCSP. + ' Siempre es distinto de Nothing, salvo cuando hay excepción. + ' + ' msjInvalidez: Nothing si la firma de la respuesta OCSP es VÁLIDA; + ' mensaje con la causa de la invalidez si es INVÁLIDA. + '----------------------------------------------------------------------------------------------------------- + + Try + + If respuestaOCSPBin Is Nothing Then + Throw New Exception("'respuestaOCSPBin' es Nothing.") + End If + + If usarResponderDeRespuestaOCSP Then + If (arrayPadresResponderAutBin Is Nothing) OrElse (arrayPadresResponderAutBin.Length = 0) Then + Throw New Exception("'usarResponderDeRespuestaOCSP' es True y 'arrayPadresResponderAutBin' es Nothing o vacío.") + End If + Else + If (arrayRespondersBin Is Nothing) OrElse (arrayRespondersBin.Length = 0) Then + Throw New Exception("'usarResponderDeRespuestaOCSP' es False y 'arrayRespondersBin' es Nothing o vacío.") + End If + End If + + '======== Lectura de la respuesta OCSP ======== + + Dim resp As New BC.Ocsp.OcspResp(respuestaOCSPBin) + + If resp.Status <> BC.Ocsp.OcspRespStatus.Successful Then Throw New Exception("Respuesta OCSP NO firmada.") + + Dim basicResp As BC.Ocsp.BasicOcspResp = CType(resp.GetResponseObject(), BC.Ocsp.BasicOcspResp) + + '============================================== + + Dim arrayPadresResponderAut As BC.X509.X509Certificate() = LeerArrayCerts(arrayPadresResponderAutBin, "arrayPadresResponderAutBin") + + Dim arrayResponders As BC.X509.X509Certificate() = LeerArrayCerts(arrayRespondersBin, "arrayRespondersBin") + + '=========== Validación de la firma =========== + + Dim responderUsado As BC.X509.X509Certificate + + Dim firmaValida As Boolean = FirmaValidaRespuestaOCSP(basicResp, + fechaUTC, arrayPadresResponderAut, + usarResponderDeRespuestaOCSP, arrayResponders, + responderUsado, msjInvalidez) + + responderBinUsado = responderUsado.GetEncoded() + + Return firmaValida + + Catch ex As Exception + responderBinUsado = Nothing + msjInvalidez = Nothing + Throw New Exception("Error ejecutando FirmaValidaRespuestaOCSP." & vbCrLf & ex.Message, ex) + End Try + + End Function + + Public Shared Function CertCorrespondeARespuestaOCSP(ByVal certBin As Byte(), + ByVal arrayCertsPadresBin As Byte()(), + ByVal respuestaOCSPBin As Byte()) As Boolean + + '---------------------------------------------------------------------------------------- + ' Devuelve True si el certificado 'certBin' corresponde a la respuesta OCSP, es decir, + ' si la información de revocación contenida en 'respuestaOCSPBin' es de 'certBin'. + '---------------------------------------------------------------------------------------- + ' Lo que se comprueba es la coincidencia del CertificateID de 'certBin' con el + ' CertificateID contenido en la respuesta OCSP. + '---------------------------------------------------------------------------------------- + ' arrayCertsPadresBin: Array con los posibles padres de 'certBin' (al menos 1). + ' Se asume que los certificados han sido VALIDADOS PREVIAMENTE. + ' La función busca el padre correspondiente. + '---------------------------------------------------------------------------------------- + + Dim cert As BC.X509.X509Certificate + Dim arrayCertsPadres As BC.X509.X509Certificate() + Dim basicResp As BC.Ocsp.BasicOcspResp + + Try + + If certBin Is Nothing Then + Throw New Exception("'certBin' es Nothing.") + End If + + If (arrayCertsPadresBin Is Nothing) OrElse (arrayCertsPadresBin.Length = 0) Then + Throw New Exception("'arrayCertsPadresBin' es Nothing o vacío.") + End If + + If respuestaOCSPBin Is Nothing Then + Throw New Exception("'respuestaOCSPBin' es Nothing.") + End If + + '============ + + cert = LeerCert(certBin, "certBin") + + arrayCertsPadres = LeerArrayCerts(arrayCertsPadresBin, "arrayCertsPadresBin") + + '============ + + Dim resp As New BC.Ocsp.OcspResp(respuestaOCSPBin) + + If resp.Status <> BC.Ocsp.OcspRespStatus.Successful Then Throw New Exception("Respuesta OCSP NO firmada.") + + basicResp = CType(resp.GetResponseObject(), BC.Ocsp.BasicOcspResp) + + Catch ex As Exception + Throw New Exception("Comprobando correspondencia del certificado con la respuesta OCSP." & vbCrLf & ex.Message, ex) + End Try + + Return CertCorrespondeARespuestaOCSP(cert, arrayCertsPadres, basicResp) + + End Function + + + Public Shared Function IntegrosCertYRespuestaOCSP(ByVal certBin As Byte(), + ByVal arrayCertsPadresBin As Byte()(), + ByVal respuestaOCSPBin As Byte(), + ByVal fechaUTC As Date, + Optional ByVal arrayArraysPadresResponderAutBin As Byte()()() = Nothing, + Optional ByVal arrayUsarResponderDeRespuestaOCSP As Boolean() = Nothing, + Optional ByVal arrayArraysRespondersBin As Byte()()() = Nothing, + Optional ByRef responderBinUsado As Byte() = Nothing, + Optional ByRef msjIntegridad As String = Nothing) As Boolean + + '------------------------------------------------------------------------------------------------------------- + ' Devuelve True si son 'íntegros' el certificado Y la respuesta OCSP, es decir, si: + ' + ' 1. La firma de 'certBin' es válida (NO se tiene en cuenta su periodo de validez), y + ' 2. La firma de 'respuestaOCSPBin' es válida (NO se tiene en cuenta su periodo (ThisUpdate, NextUpdate)), y + ' 3. 'certBin' corresponde a 'respuestaOCSPBin'. + ' + '------------------------------------------------------------------------------------------------------------- + ' arrayCertsPadresBin: Array con los posibles padres de 'certBin' (al menos 1). + ' Se asume que los certificados han sido VALIDADOS PREVIAMENTE. + ' La función busca el padre correspondiente. + ' + ' fechaUTC: Fecha en la que se validará el Responder Autorizado contenido en la respuesta OCSP. + ' + ' arrayArraysPadresResponderAutBin: Mismo significado que en ConsultarInfoRevoc(). + ' arrayUsarResponderDeRespuestaOCSP: Mismo significado que en ConsultarInfoRevoc(). + ' arrayArraysRespondersBin: Mismo significado que en ConsultarInfoRevoc(). + '------------------------------------------------------------------------------------------------------------- + ' responderBinUsado: Se devuelve el certificado Responder usado para validar la firma de la respuesta OCSP. + ' Puede ser Nothing. + ' + ' msjIntegridad: Nothing si se devuelve True; + ' mensaje con la causa del problema de integridad si se devuelve False. + '------------------------------------------------------------------------------------------------------------- + + Try + + If certBin Is Nothing Then + Throw New Exception("'certBin' es Nothing.") + End If + + If (arrayCertsPadresBin Is Nothing) OrElse (arrayCertsPadresBin.Length = 0) Then + Throw New Exception("'arrayCertsPadresBin' es Nothing o vacío.") + End If + + If respuestaOCSPBin Is Nothing Then + Throw New Exception("'respuestaOCSPBin' es Nothing.") + End If + + + If (arrayArraysPadresResponderAutBin IsNot Nothing) AndAlso (arrayArraysPadresResponderAutBin.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayArraysPadresResponderAutBin' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + If (arrayUsarResponderDeRespuestaOCSP IsNot Nothing) AndAlso (arrayUsarResponderDeRespuestaOCSP.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayUsarResponderDeRespuestaOCSP' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + If (arrayArraysRespondersBin IsNot Nothing) AndAlso (arrayArraysRespondersBin.Length <> arrayCertsPadresBin.Length) Then + Throw New Exception("'arrayArraysRespondersBin' NO tiene la misma longitud que 'arrayCertsPadresBin'.") + End If + + + msjIntegridad = "Comprobando integridad del certificado y de la respuesta OCSP." & vbCrLf + + '===== Validación de la firma del certificado ===== + + Dim cert As BC.X509.X509Certificate = LeerCert(certBin, "certBin") + Dim arrayCertsPadres As BC.X509.X509Certificate() = LeerArrayCerts(arrayCertsPadresBin, "arrayCertsPadresBin") + + ' La búsqueda comprueba la firma de 'cert': + Dim iPadre As Integer = BuscarCertPadre(cert, arrayCertsPadres) + + If iPadre = -1 Then + responderBinUsado = Nothing + msjIntegridad &= "No se ha encontrado certificado emisor para el certificado de:" & vbCrLf & + " " & cert.SubjectDN.ToString() + Return False + End If + + Dim certPadre As BC.X509.X509Certificate = arrayCertsPadres(iPadre) + + '========== Lectura de la respuesta OCSP ========== + + Dim resp As New BC.Ocsp.OcspResp(respuestaOCSPBin) + + If resp.Status <> BC.Ocsp.OcspRespStatus.Successful Then Throw New Exception("Respuesta OCSP NO firmada.") + + Dim basicResp As BC.Ocsp.BasicOcspResp = CType(resp.GetResponseObject(), BC.Ocsp.BasicOcspResp) + + '================================================== + + Dim arrayPadresResponderAutBin As Byte()() = Nothing + Dim usarResponderDeRespuestaOCSP As Boolean = True + Dim arrayRespondersBin As Byte()() = Nothing + + If arrayArraysPadresResponderAutBin IsNot Nothing Then + arrayPadresResponderAutBin = arrayArraysPadresResponderAutBin(iPadre) + End If + + If arrayUsarResponderDeRespuestaOCSP IsNot Nothing Then + usarResponderDeRespuestaOCSP = arrayUsarResponderDeRespuestaOCSP(iPadre) + End If + + If arrayArraysRespondersBin IsNot Nothing Then + arrayRespondersBin = arrayArraysRespondersBin(iPadre) + End If + + '======= Lectura de arrayPadresResponderAut ======= + + Dim arrayPadresResponderAut As BC.X509.X509Certificate() + + If arrayPadresResponderAutBin Is Nothing Then + arrayPadresResponderAut = {certPadre} + Else + arrayPadresResponderAut = LeerArrayCerts(arrayPadresResponderAutBin, "arrayPadresResponderAutBin") + End If + + '=========== Lectura de arrayResponders =========== + + Dim arrayResponders As BC.X509.X509Certificate() + + If arrayRespondersBin Is Nothing Then + arrayResponders = {certPadre} + Else + arrayResponders = LeerArrayCerts(arrayRespondersBin, "arrayRespondersBin") + End If + + '===== Validación de la firma de la respuesta ===== + + Dim responderUsado As BC.X509.X509Certificate + Dim msjInvalidezFirmaResp As String + + Dim firmaValidaResp As Boolean = FirmaValidaRespuestaOCSP(basicResp, + fechaUTC, arrayPadresResponderAut, + usarResponderDeRespuestaOCSP, arrayResponders, + responderUsado, msjInvalidezFirmaResp) + + responderBinUsado = responderUsado.GetEncoded() + + If Not firmaValidaResp Then + msjIntegridad &= msjInvalidezFirmaResp + Return False + End If + + '======== Comprobación de correspondencia ========= + + If Not CertCorrespondeARespuestaOCSP(cert, {certPadre}, basicResp) Then + msjIntegridad &= "El certificado de: " & cert.SubjectDN.ToString() & vbCrLf & + "NO corresponde a la respuesta OCSP." + Return False + End If + + '================================================== + + msjIntegridad = Nothing + Return True + + Catch ex As Exception + responderBinUsado = Nothing + msjIntegridad = Nothing + Throw New Exception("Comprobando integridad del certificado y de la respuesta OCSP." & vbCrLf & ex.Message, ex) + End Try + + End Function + + Public Shared Function RespuestaOCSPToString(ByVal respuestaOCSPBin As Byte(), + Optional ByVal separador As String = vbCrLf) As String + + Dim res As String + Try + + If respuestaOCSPBin Is Nothing Then + Throw New Exception("'respuestaOCSPBin' es Nothing.") + End If + + '============== + + Dim resp As New BC.Ocsp.OcspResp(respuestaOCSPBin) + + res = "responseStatus: " & resp.Status + + If resp.Status = BC.Ocsp.OcspRespStatus.Successful Then + + Dim basicResp As BC.Ocsp.BasicOcspResp = CType(resp.GetResponseObject(), BC.Ocsp.BasicOcspResp) + + res &= separador & "signatureAlgorithmOID: " & basicResp.SignatureAlgOid + res &= separador & "signatureAlgorithmName: " & basicResp.SignatureAlgName + + Dim certsResponder As BC.X509.X509Certificate() = basicResp.GetCerts() + + res &= separador & "numCertsResponder: " & If(certsResponder Is Nothing, 0, certsResponder.Length) + + Dim authorizedResponder As BC.X509.X509Certificate + Try + authorizedResponder = BuscarResponderAutorizado(certsResponder) + Catch ex As Exception + authorizedResponder = Nothing + End Try + + res &= separador & "authorizedResponder: " & If(authorizedResponder Is Nothing, "", authorizedResponder.SubjectDN.ToString()) + res &= separador & "producedAtUTC: " & DateToString(basicResp.ProducedAt) + res &= separador & "numResponseExtensions: " & If(basicResp.ResponseExtensions Is Nothing, 0, basicResp.ResponseExtensions.GetExtensionOids().Length) + + Dim numResponses As Integer = If(basicResp.Responses Is Nothing, 0, basicResp.Responses.Length) + res &= separador & "numResponses: " & numResponses + + ' Sólo mostramos la primera SingleResponse: + If numResponses >= 1 Then + + Dim singleResp As BC.Ocsp.SingleResp = basicResp.Responses(0) + + res &= separador & "certSerialNumber: " & Array2Hex(singleResp.GetCertID().SerialNumber.ToByteArray(), " ") + + Dim certStatusObj As Object = singleResp.GetCertStatus() + + If certStatusObj Is BC.Ocsp.CertificateStatus.Good Then ' Equivale a Is Nothing + res &= separador & "certStatus: 0 (good)" + ElseIf TypeOf certStatusObj Is BC.Ocsp.RevokedStatus Then + res &= separador & "certStatus: 1 (revoked)" + Dim revInfo As BC.Ocsp.RevokedStatus = CType(certStatusObj, BC.Ocsp.RevokedStatus) + res &= separador & "revocationTimeUTC: " & DateToString(revInfo.RevocationTime) + res &= separador & "revocationReason: " & If(revInfo.HasRevocationReason, revInfo.RevocationReason, -1) + ElseIf TypeOf certStatusObj Is BC.Ocsp.UnknownStatus Then + res &= separador & "certStatus: 2 (unknown)" + Else + res &= separador & "certStatus: -1" + End If + + res &= separador & "thisUpdateUTC: " & DateToString(singleResp.ThisUpdate) + res &= separador & "nextUpdateUTC: " & If(singleResp.NextUpdate Is Nothing, "", + DateToString(singleResp.NextUpdate.Value)) + + res &= separador & "numSingleExtensions: " & If(singleResp.SingleExtensions Is Nothing, 0, singleResp.SingleExtensions.GetExtensionOids().Length) + + End If + + End If + + Catch ex As Exception + Throw New Exception("Error ejecutando RespuestaOCSPToString." & vbCrLf & ex.Message, ex) + End Try + + Return res + + End Function + + Public Shared Function LeerCamposRespuestaOCSP(ByVal respuestaOCSPBin As Byte()) As CamposRespuestaOCSP + + Dim campos As CamposRespuestaOCSP + Try + + If respuestaOCSPBin Is Nothing Then + Throw New Exception("'respuestaOCSPBin' es Nothing.") + End If + + '=========== + + campos = New CamposRespuestaOCSP() + + Dim resp As New BC.Ocsp.OcspResp(respuestaOCSPBin) + + campos.responseStatus = resp.Status + + If resp.Status = BC.Ocsp.OcspRespStatus.Successful Then + + Dim basicResp As BC.Ocsp.BasicOcspResp = CType(resp.GetResponseObject(), BC.Ocsp.BasicOcspResp) + + campos.signatureAlgorithmOID = basicResp.SignatureAlgOid + campos.signatureAlgorithmName = basicResp.SignatureAlgName + + Dim certsResponder As BC.X509.X509Certificate() = basicResp.GetCerts() + + campos.numCertsResponder = If(certsResponder Is Nothing, 0, certsResponder.Length) + + Dim authorizedResponder As BC.X509.X509Certificate + Try + authorizedResponder = BuscarResponderAutorizado(certsResponder) + Catch ex As Exception + authorizedResponder = Nothing + End Try + + campos.authorizedResponderBin = If(authorizedResponder Is Nothing, Nothing, authorizedResponder.GetEncoded()) + campos.producedAtUTC = basicResp.ProducedAt + campos.numResponseExtensions = If(basicResp.ResponseExtensions Is Nothing, 0, basicResp.ResponseExtensions.GetExtensionOids().Length) + campos.numResponses = If(basicResp.Responses Is Nothing, 0, basicResp.Responses.Length) + + ' Sólo leemos la primera SingleResponse: + If campos.numResponses >= 1 Then + + Dim singleResp As BC.Ocsp.SingleResp = basicResp.Responses(0) + + campos.certSerialNumber = singleResp.GetCertID().SerialNumber.ToByteArray() + + Dim certStatusObj As Object = singleResp.GetCertStatus() + + If certStatusObj Is BC.Ocsp.CertificateStatus.Good Then ' Equivale a Is Nothing + campos.certStatus = EstadoRevocEnum.NoRevocado + ElseIf TypeOf certStatusObj Is BC.Ocsp.RevokedStatus Then + campos.certStatus = EstadoRevocEnum.Revocado + Dim revInfo As BC.Ocsp.RevokedStatus = CType(certStatusObj, BC.Ocsp.RevokedStatus) + campos.revocationTimeUTC = revInfo.RevocationTime + campos.revocationReason = If(revInfo.HasRevocationReason, revInfo.RevocationReason, -1) + ElseIf TypeOf certStatusObj Is BC.Ocsp.UnknownStatus Then + campos.certStatus = EstadoRevocEnum.Indeterminado + Else + campos.certStatus = -1 + End If + + campos.thisUpdateUTC = singleResp.ThisUpdate + campos.nextUpdateUTC = If(singleResp.NextUpdate Is Nothing, Nothing, singleResp.NextUpdate.Value) + + campos.numSingleExtensions = If(singleResp.SingleExtensions Is Nothing, 0, singleResp.SingleExtensions.GetExtensionOids().Length) + + End If + + End If + + Catch ex As Exception + Throw New Exception("Leyendo campos de la respuesta OCSP." & vbCrLf & ex.Message, ex) + End Try + + Return campos + + End Function + + + Public Shared Function ValidoCertSinRevoc(ByVal certBin As Byte(), + ByVal certPadreBin As Byte(), + ByVal fechaUTC As Date, + Optional ByRef msjInvalidez As String = Nothing) As Boolean + + '-------------------------------------------------------------------- + ' No se comprueba el estado de revocación de 'certBin'. + ' Se asume que 'certPadreBin' ha sido VALIDADO PREVIAMENTE. + '-------------------------------------------------------------------- + ' msjInvalidez: Nothing si 'certBin' es VÁLIDO; + ' mensaje con la causa de la invalidez si es INVÁLIDO. + '-------------------------------------------------------------------- + + Dim cert As BC.X509.X509Certificate + Dim certPadre As BC.X509.X509Certificate + + Try + + cert = LeerCert(certBin, "certBin") + certPadre = LeerCert(certPadreBin, "certPadreBin") + + Catch ex As Exception + msjInvalidez = Nothing + Throw New Exception("Validando certificado." & vbCrLf & ex.Message, ex) + End Try + + Return ValidoCertSinRevoc(cert, {certPadre}, fechaUTC, msjInvalidez) + + End Function + + Public Shared Function ValidoCertSinRevoc(ByVal certBin As Byte(), + ByVal arrayCertsPadresBin As Byte()(), + ByVal fechaUTC As Date, + Optional ByRef msjInvalidez As String = Nothing) As Boolean + + '-------------------------------------------------------------------- + ' No se comprueba el estado de revocación de 'certBin'. + ' 'arrayCertsPadresBin' contiene los posibles padres (al menos 1). + ' Se asume que los padres han sido VALIDADOS PREVIAMENTE. + '-------------------------------------------------------------------- + ' msjInvalidez: Nothing si 'certBin' es VÁLIDO según algún padre; + ' mensaje con la causa de la invalidez si es INVÁLIDO. + '-------------------------------------------------------------------- + + Dim cert As BC.X509.X509Certificate + Dim arrayCertsPadres As BC.X509.X509Certificate() + + Try + + cert = LeerCert(certBin, "certBin") + arrayCertsPadres = LeerArrayCerts(arrayCertsPadresBin, "arrayCertsPadresBin") + + Catch ex As Exception + msjInvalidez = Nothing + Throw New Exception("Validando certificado." & vbCrLf & ex.Message, ex) + End Try + + Return ValidoCertSinRevoc(cert, arrayCertsPadres, fechaUTC, msjInvalidez) + + End Function + + + Public Shared Function BuscarCertPadre(ByVal certBin As Byte(), + ByVal arrayCertsPadresBin As Byte()()) As Integer + + '-------------------------------------------------------------------- + ' RESULTADO: Índice del padre de 'certBin' en 'arrayCertsPadresBin', + ' o -1 si no se encuentra. + '-------------------------------------------------------------------- + ' Se comprueban DNs y FIRMA CRIPTOGRÁFICA. + '-------------------------------------------------------------------- + + Dim cert As BC.X509.X509Certificate + Dim arrayCertsPadres As BC.X509.X509Certificate() + + Try + + cert = LeerCert(certBin, "certBin") + arrayCertsPadres = LeerArrayCerts(arrayCertsPadresBin, "arrayCertsPadresBin") + + Catch ex As Exception + Throw New Exception("Buscando certificado emisor." & vbCrLf & ex.Message, ex) + End Try + + Return BuscarCertPadre(cert, arrayCertsPadres) + + End Function + + + Public Shared Function ObtenerValorAtributoDN(ByVal certBin As Byte(), + Optional ByVal oidStr As String = "2.5.4.3", + Optional ByVal delSubject As Boolean = True) As String + + '------------------------------------------------------------------------------------------------------------ + ' Devuelve el valor de un atributo de un DN (DistinguishedName: Subject o Issuer) del certificado 'certBin'. + ' Por defecto del DN Subject (del DN Issuer si 'delSubject' = False). + '------------------------------------------------------------------------------------------------------------ + ' Se devuelve NOTHING si NO existe el atributo. + ' Se eleva EXCEPCIÓN si hay más de 1 atributo con el OID 'oidStr'. + '------------------------------------------------------------------------------------------------------------ + ' oidStr: String con el OID del atributo cuyo valor se desea obtener. Por defecto commonName (2.5.4.3). + ' Ejemplos: + ' + ' commonName (CN): oidStr = 2.5.4.3 (suele ser: ... + APELLIDOS + NOMBRE + ...) + ' surname (SN): oidStr = 2.5.4.4 (apellidos) + ' givenName (G): oidStr = 2.5.4.42 (nombre propio) + ' serialNumber: oidStr = 2.5.4.5 (suele ser el DNI: "12345678Z" o "IDCES-12345678Z") + ' organizationIdentifier: oidStr = 2.5.4.97 (suele ser el CIF: "VATES-A12345678") + ' organizationName (O): oidStr = 2.5.4.10 (suele ser la Organización o la Razón Social) + ' organizationalUnitName (OU): oidStr = 2.5.4.11 (suele ser un Departamento/Sección; a veces múltiple) + ' countryName (C): oidStr = 2.5.4.6 (código del país (2 caracteres): "ES") + ' + ' NOTA: IDCES = Identity Document Card ESpaña + ' VATES = Value Added Tax identifier ESpaña + '------------------------------------------------------------------------------------------------------------ + + Try + + Dim parser As New BC.X509.X509CertificateParser() + Dim cert As BC.X509.X509Certificate = parser.ReadCertificate(certBin) + + Dim dn As BC.Asn1.X509.X509Name + + If delSubject Then + dn = cert.SubjectDN + Else + dn = cert.IssuerDN + End If + + Dim list As IList = dn.GetValueList(New BC.Asn1.DerObjectIdentifier(oidStr)) + + If (list Is Nothing) OrElse (list.Count = 0) Then Return Nothing + + If list.Count > 1 Then Throw New Exception("Se ha encontrado más de 1 atributo: Count = " & list.Count) + + Return If(list(0) Is Nothing, "", CStr(list(0))) + + Catch ex As Exception + Throw New Exception("Obteniendo valor de un atributo del " & If(delSubject, "Subject", "Issuer") & ": OID = " & oidStr & vbCrLf & ex.Message, ex) + End Try + + End Function + + + 'Public Shared Function ObtenerCertsDesdeAlmacenesWindows(Optional ByVal mensajeInteractivo As String = Nothing, + ' Optional ByVal fechaReferencia As Date = Nothing, + ' Optional ByVal filtroIssuer As String = Nothing, + ' Optional ByVal filtroSubject As String = Nothing, + ' Optional ByVal soloConPrivada As Boolean = True, + ' Optional ByVal nombresAlmacenes As SSC.X509Certificates.StoreName() = Nothing, + ' Optional ByVal seleccionInteractiva As Boolean = True, + ' Optional ByVal exigirResultadoUnico As Boolean = True, + ' Optional ByVal incluirLocalMachine As Boolean = False) _ + ' As SSC.X509Certificates.X509Certificate2Collection + + ' '---------------------------------------------------------------------------------------------------------- + ' ' Obtiene certificado(s) desde los almacenes de certificados de Windows. + ' '---------------------------------------------------------------------------------------------------------- + ' ' mensajeInteractivo: Mensaje mostrado al usuario. + ' ' Nothing para mostrar un mensaje por defecto. + ' ' + ' ' fechaReferencia: Se filtrarán los certificados vigentes en 'fechaReferencia' (hora LOCAL). + ' ' Nothing para no filtrar por fecha. + ' ' + ' ' filtroIssuer: Se filtrarán los certificados que contengan este string en el DN Issuer. + ' ' Nothing para no filtrar por el Issuer. + ' ' No se tienen en cuenta mayúsculas/minúsculas. + ' ' + ' ' filtroSubject: Se filtrarán los certificados que contengan este string en el DN Subject. + ' ' Nothing para no filtrar por el Subject. + ' ' No se tienen en cuenta mayúsculas/minúsculas. + ' ' + ' ' soloConPrivada: Si es True entonces se filtrarán los certificados con clave privada. + ' ' + ' ' nombresAlmacenes: Nombres de los almacenes de certificados a los que se accederá. + ' ' Nothing para acceder a los almacenes por defecto ("Personal" y "Otras personas"). + ' ' + ' ' seleccionInteractiva: Si es True entonces se mostrará al usuario una ventana de selección. + ' ' Si es False entonces se devolverá el contenido filtrado de los almacenes. + ' ' + ' ' exigirResultadoUnico: Si es True entonces se exigirá que el resultado contenga exactamente 1 certificado, + ' ' elevándose EXCEPCIÓN en caso contrario. + ' ' + ' ' incluirLocalMachine: Si es False entonces sólo se usará "Current User". + ' ' Si es True entonces se incluirá "Local Machine" ADEMÁS de "Current User". + ' ' En este caso podrán aparecer certificados duplicados si se usan almacenes + ' ' distintos de "Personal" (por ejemplo "Raíces de confianza"), ya que los + ' ' almacenes de "Current User" distintos de "Personal" heredan el contenido + ' ' de los almacenes de "Local Machine". + ' '---------------------------------------------------------------------------------------------------------- + + ' Try + + ' If nombresAlmacenes Is Nothing Then + ' nombresAlmacenes = {SSC.X509Certificates.StoreName.My, + ' SSC.X509Certificates.StoreName.AddressBook} + ' ElseIf nombresAlmacenes.Length = 0 Then + ' Throw New Exception("'nombresAlmacenes' tiene 0 elementos.") + ' End If + + ' '------ Extracción de certificados desde los almacenes ------ + + ' Dim i As Integer + ' Dim st As SSC.X509Certificates.X509Store + + ' Dim certCol As New SSC.X509Certificates.X509Certificate2Collection() + + ' For i = 0 To (nombresAlmacenes.Length - 1) + + ' ' Current User: + ' st = New SSC.X509Certificates.X509Store(nombresAlmacenes(i), SSC.X509Certificates.StoreLocation.CurrentUser) + ' st.Open(SSC.X509Certificates.OpenFlags.ReadOnly Or SSC.X509Certificates.OpenFlags.IncludeArchived) + + ' certCol.AddRange(st.Certificates) + + ' st.Close() + + ' If incluirLocalMachine Then + ' ' Local Machine: + ' st = New SSC.X509Certificates.X509Store(nombresAlmacenes(i), SSC.X509Certificates.StoreLocation.LocalMachine) + ' st.Open(SSC.X509Certificates.OpenFlags.ReadOnly Or SSC.X509Certificates.OpenFlags.IncludeArchived) + + ' certCol.AddRange(st.Certificates) + + ' st.Close() + ' End If + + ' Next + + ' '------ Filtrado de la colección de certificados ------ + + ' If fechaReferencia <> Nothing Then + ' certCol = certCol.Find(SSC.X509Certificates.X509FindType.FindByTimeValid, fechaReferencia, False) + ' End If + + ' If filtroIssuer IsNot Nothing Then + ' certCol = certCol.Find(SSC.X509Certificates.X509FindType.FindByIssuerName, filtroIssuer, False) + ' End If + + ' If filtroSubject IsNot Nothing Then + ' certCol = certCol.Find(SSC.X509Certificates.X509FindType.FindBySubjectName, filtroSubject, False) + ' End If + + ' If soloConPrivada Then + ' For i = (certCol.Count - 1) To 0 Step -1 + ' If Not certCol(i).HasPrivateKey Then certCol.RemoveAt(i) + ' Next + ' End If + + ' '------ Selección interactiva del certificado ------ + + ' If seleccionInteractiva Then + + ' If mensajeInteractivo Is Nothing Then + ' ' Mensaje por defecto: + ' mensajeInteractivo = "Se muestran los siguientes certificados:" & vbCrLf & + ' " Almacenes: " & StoreNameArrayToString(nombresAlmacenes) & vbCrLf & + ' " Vigencia: " & If(fechaReferencia = Nothing, "Cualquiera", "En " & DateToString(fechaReferencia)) & vbCrLf & + ' " Sólo con clave privada: " & If(soloConPrivada, "Sí", "No") & vbCrLf & + ' " Filtro de Titular: " & If(filtroSubject Is Nothing, "Todos", filtroSubject) & vbCrLf & + ' " Filtro de Emisor: " & If(filtroIssuer Is Nothing, "Todos", filtroIssuer) + ' End If + + ' certCol = SSC.X509Certificates.X509Certificate2UI.SelectFromCollection(certCol, + ' If(exigirResultadoUnico, + ' "SELECCIONE UN CERTIFICADO", + ' "SELECCIONE CERTIFICADO(S)"), + ' mensajeInteractivo, + ' If(exigirResultadoUnico, + ' SSC.X509Certificates.X509SelectionFlag.SingleSelection, + ' SSC.X509Certificates.X509SelectionFlag.MultiSelection)) + + ' End If + + ' '------ Devolución del resultado ------ + + ' If exigirResultadoUnico AndAlso (certCol.Count <> 1) Then + ' Throw New Exception("Se exige UN certificado.") + ' End If + + ' Return certCol + + ' Catch ex As Exception + ' Throw New Exception("Importando certificado(s) desde Almacenes de Certificados de Windows: " & + ' StoreNameArrayToString(nombresAlmacenes) & vbCrLf & + ' ex.Message, ex) + ' End Try + + 'End Function + + + Public Shared Function EstadoRevocToString(ByVal estadoRevoc As EstadoRevocEnum) As String + + Dim res As String + + Select Case estadoRevoc + Case EstadoRevocEnum.NoRevocado : res = "Correcto (no revocado)" + Case EstadoRevocEnum.Revocado : res = "Revocado" + Case EstadoRevocEnum.Indeterminado : res = "Indeterminado" + Case Else + res = "Valor desconocido de estado de revocación: " & estadoRevoc + End Select + + Return res + + End Function + + Public Shared Function RevocationReasonToString(ByVal reason As Integer) As String + + '---------------------------------------------- + ' reason: Código de la causa de la revocación. + '---------------------------------------------- + + Dim res As String + + Select Case reason + Case 0 : res = "Causa no especificada" + Case 1 : res = "Clave comprometida" + Case 2 : res = "Autoridad Certificadora comprometida" + Case 3 : res = "Cambio de afiliación" + Case 4 : res = "Sustitución del certificado" + Case 5 : res = "Cese de operación del Emisor" + Case 6 : res = "Suspensión del certificado" + Case 8 : res = "Acción: Eliminar de CRL tras suspensión" + Case 9 : res = "Retirada de privilegios" + Case 10 : res = "Autoridad de Autorización comprometida" + Case Else + res = If(reason = -1, "Causa no disponible", "Código no catalogado") + End Select + + res &= " (CodRev=" & reason & ")" + + Return res + + End Function + + Public Shared Function StoreNameToString(ByVal sn As SSC.X509Certificates.StoreName) As String + + '--------------------------------------------------------------------------------- + ' Traduce 'sn' (valor de la enumeración StoreName) a su representación en string. + '--------------------------------------------------------------------------------- + + Dim res As String + + Select Case sn + Case SSC.X509Certificates.StoreName.AddressBook : res = "Otras personas" + Case SSC.X509Certificates.StoreName.AuthRoot : res = "Otras CAs" + Case SSC.X509Certificates.StoreName.CertificateAuthority : res = "CAs intermedias" + Case SSC.X509Certificates.StoreName.Disallowed : res = "Certificados revocados" + Case SSC.X509Certificates.StoreName.My : res = "Personal" + Case SSC.X509Certificates.StoreName.Root : res = "Raíces de confianza" + Case SSC.X509Certificates.StoreName.TrustedPeople : res = "Personas de confianza directa" + Case SSC.X509Certificates.StoreName.TrustedPublisher : res = "Emisores de confianza directa" + Case Else + res = "" + End Select + + Return res + + End Function + + Public Shared Function StoreNameArrayToString(ByVal snArray As SSC.X509Certificates.StoreName()) As String + + '----------------------------------------------------------- + ' Traduce el array 'snArray' a su representación en string. + '----------------------------------------------------------- + + If snArray Is Nothing Then Return "" + + Dim s(snArray.Length - 1) As String + + Dim i As Integer + For i = 0 To (snArray.Length - 1) + s(i) = StoreNameToString(snArray(i)) + Next + + Return ArrayToString(s, , , ", ") + + End Function + + Public Shared Function DateToString(ByVal d As Date) As String + + Return d.ToString("dd\/MM\/yyyy HH\:mm\:ss") + + End Function + + Public Shared Function Array2Hex(ByVal a As Byte(), + Optional ByVal separador As String = "") As String + + '-------------------------------------------------------------------------------------------------- + ' Transforma un array de bytes a un string en hexadecimal (cada byte se representa en hexadecimal). + ' Los caracteres alfabéticos hexadecimales SIEMPRE SE REPRESENTAN EN MAYÚSCULAS (ABCDEF). + '-------------------------------------------------------------------------------------------------- + ' 'separador' es el string separador de bytes (POR DEFECTO VACÍO). + '-------------------------------------------------------------------------------------------------- + + Dim res As String = "" + + If (a IsNot Nothing) AndAlso (a.Length > 0) Then + + Dim i As Integer + + For i = 0 To (a.Length - 2) + res &= [String].Format("{0:X2}", a(i)) & separador + Next + + res &= [String].Format("{0:X2}", a(a.Length - 1)) + + End If + + Return res + + End Function + + Public Shared Function ArrayToString(ByVal a As Array, + Optional ByVal lineNumberDigits As Integer = 0, + Optional ByVal globalIndent As String = "", + Optional ByVal separator As String = vbCrLf, + Optional ByVal baseLineNumber As Integer = 1) As String + + '----------------------------------------------------------------------------- + ' Transforma el array UNIDIMENSIONAL 'a' a su representación en string. + ' Se usa el método ToString() de cada elemento del array. + '----------------------------------------------------------------------------- + ' lineNumberDigits --> Número de digitos reservados para el número de línea. + ' Si es 0 no se mostrará número de línea. + ' globalIndent --> String que se usa como indentación de todas las líneas. + ' separator --> Separador de elementos del array (NO A LA COLA). + ' baseLineNumber --> Número de línea inicial. + '----------------------------------------------------------------------------- + + If a Is Nothing Then Return globalIndent & "" + + If a.Rank <> 1 Then Throw New Exception("ArrayToString: Array NO unidimensional.") + + If a.Length = 0 Then Return globalIndent & "" + + '------ + + ' Usamos un StringBuilder por eficiencia (la lista puede ser larga): + Dim sb As New System.Text.StringBuilder + + Dim elemStr As String ' Representación en string de un elemento de 'a' + + Dim num As String ' String con el número de elemento + Dim localIndent As String ' Indentación local a cada elemento + + Dim i As Integer + For i = 0 To (a.Length - 1) + If i = (a.Length - 1) Then separator = "" + + If a(i) Is Nothing Then + elemStr = "" + Else + elemStr = a(i).ToString() + End If + + If lineNumberDigits > 0 Then + num = (i + baseLineNumber).ToString().PadLeft(lineNumberDigits) & ". " + localIndent = New String(" "c, num.Length) + + sb.Append(globalIndent & num & elemStr.Replace(vbCrLf, vbCrLf & globalIndent & localIndent) & separator) + Else + sb.Append(globalIndent & elemStr.Replace(vbCrLf, vbCrLf & globalIndent) & separator) + End If + + Next + + Return sb.ToString() + + End Function + + Public Shared Function Indent(ByVal text As String, + Optional ByVal numberOfSpaces As Integer = 4, + Optional ByVal indentPrefix As String = "", + Optional ByVal indentFirstLine As Boolean = True) As String + + '------------------------------------------------------------------------------------ + ' Indenta 'text' el número de espacios 'numberOfSpaces', devolviendo el nuevo string. + ' 'indentPrefix' SE AÑADE al principio de cada línea indentada. + ' Si 'indentFirstLine' es False NO se indenta la primera línea de 'text'. + '------------------------------------------------------------------------------------ + + If text Is Nothing Then text = "" + If numberOfSpaces < 0 Then Throw New Exception("Indent: 'numberOfSpaces' < 0") + + Dim indentStr As String = indentPrefix & New String(" "c, numberOfSpaces) + + Return If(indentFirstLine, indentStr, "") & text.Replace(vbCrLf, vbCrLf & indentStr) + + End Function + + + Public Shared Function Fichero2Array(ByVal nombreFich As String, + Optional ByVal mensajeError As String = Nothing, + Optional ByVal limiteMaxBytes As Long = Long.MaxValue, + Optional ByVal limiteMinBytes As Long = 0) As Byte() + + '-------------------------------------------------------------------- + ' Lee del fichero 'nombreFich' todo su contenido como array de bytes. + '-------------------------------------------------------------------- + ' 'mensajeError' se mostrará en los mensajes de EXCEPCIONES. + ' 'limiteMaxBytes' es la longitud máxima que se permite al fichero. + ' 'limiteMinBytes' es la longitud mínima que se permite al fichero. + ' Si NO se cumplen los límites NO se lee nada y se eleva EXCEPCIÓN. + '-------------------------------------------------------------------- + + Dim res As Byte() + + Dim fsFich As FileStream + Try + + fsFich = New FileStream(nombreFich, FileMode.Open, FileAccess.Read) + + Dim fsFichLen As Long = fsFich.Length + + If fsFichLen > limiteMaxBytes Then + Throw New Exception("Fichero demasiado grande: " & fsFichLen & " Bytes") + End If + + If fsFichLen < limiteMinBytes Then + Throw New Exception("Fichero demasiado pequeño: " & fsFichLen & " Bytes") + End If + + Dim brFich As New BinaryReader(fsFich) + + res = brFich.ReadBytes(fsFichLen) + + If (fsFich.Length <> fsFichLen) OrElse (res.Length <> fsFichLen) Then + Throw New Exception("ERROR GRAVE: Longitudes de fichero y de información leída incoherentes.") + End If + + Catch ex As Exception + Throw New Exception(If(mensajeError = "", "", mensajeError & vbCrLf) & + "Leyendo array de bytes de fichero: " & nombreFich & vbCrLf & + ex.Message, ex) + Finally + If fsFich IsNot Nothing Then fsFich.Close() + End Try + + Return res + + End Function + + + Public Shared Function CalcularSHA1Str(ByVal a As Byte()) As String + + '---------------------------------------------------------------------------------- + ' Devuelve el hash SHA-1 de 'a' como string hexadecimal (MAYÚSCULAS) sin espacios. + '---------------------------------------------------------------------------------- + + Try + + If a Is Nothing Then + Throw New Exception("El array 'a' es Nothing.") + End If + + Dim cspSHA1 As New SSC.SHA1CryptoServiceProvider() + + Dim hashSHA1 As Byte() = cspSHA1.ComputeHash(a) + + Return Array2Hex(hashSHA1) + + Catch ex As Exception + Throw New Exception("Calculando hash SHA-1 (String)." & vbCrLf & ex.Message, ex) + End Try + + End Function + +#End Region + +#Region "Métodos privados compartidos (Shared)" + + Private Shared Sub ConsultarOCSP(ByVal certBin As Byte(), + ByVal certPadreBin As Byte(), + ByVal usarUrlResponderDeCert As Boolean, + ByVal urlResponder As String, + ByVal arrayPadresResponderAutBin As Byte()(), + ByVal usarResponderDeRespuestaOCSP As Boolean, + ByVal arrayRespondersBin As Byte()(), + ByVal timeoutSegundos As Integer, + ByVal requestorBin As Byte(), + ByVal chainRequestorBin As Byte()(), + ByVal privadaRequestor As SSC.RSAParameters, + ByRef reintentable As Boolean, + ByRef infoRev As InfoRevoc) + + '-------------------------------------------------------------------------------------- + ' Realiza una única consulta OCSP. + '-------------------------------------------------------------------------------------- + ' reintentable: Se devuelve True si después de esta petición tiene sentido reintentar. + '-------------------------------------------------------------------------------------- + + Try + + If certBin Is Nothing Then Throw New Exception("'certBin' es Nothing.") + If certPadreBin Is Nothing Then Throw New Exception("'certPadreBin' es Nothing.") + + If (Not usarUrlResponderDeCert) AndAlso (urlResponder = "") Then + Throw New Exception("'usarUrlResponderDeCert' es False y 'urlResponder' está vacía.") + End If + + If usarResponderDeRespuestaOCSP Then + If (arrayPadresResponderAutBin IsNot Nothing) AndAlso (arrayPadresResponderAutBin.Length = 0) Then + Throw New Exception("'usarResponderDeRespuestaOCSP' es True y 'arrayPadresResponderAutBin' es vacío.") + End If + Else + If (arrayRespondersBin IsNot Nothing) AndAlso (arrayRespondersBin.Length = 0) Then + Throw New Exception("'usarResponderDeRespuestaOCSP' es False y 'arrayRespondersBin' es vacío.") + End If + End If + + If timeoutSegundos < 1 Then Throw New Exception("'timeoutSegundos' es menor que 1.") + + '-------- + + infoRev = New InfoRevoc() + + infoRev.CertBin = certBin + + infoRev.EstadoRevoc = EstadoRevocEnum.Indeterminado + infoRev.MsjEstadoRevoc = Nothing + infoRev.FechaRevocUTC = Nothing + + infoRev.ThisUpdateUTC = Nothing + infoRev.NextUpdateUTC = Nothing + + infoRev.RespuestaOCSPBin = Nothing + infoRev.RespuestaEfectiva = False + infoRev.UrlResponder = Nothing + infoRev.ResponderBin = Nothing + + '======== Lectura de cert y de certPadre ======== + + Dim cert As BC.X509.X509Certificate = LeerCert(certBin, "certBin") + Dim certPadre As BC.X509.X509Certificate = LeerCert(certPadreBin, "certPadreBin") + + ' Para que la consulta OCSP sea correcta es MUY IMPORTANTE asegurarse + ' de que 'certPadre' realmente es el padre de 'cert', tanto nominalmente + ' como criptográficamente: + + If Not cert.IssuerDN.Equivalent(certPadre.SubjectDN, True) Then + Throw New Exception("'certPadre' NO es el emisor de 'cert'.") + End If + + If Not FirmaValidaCert(cert, certPadre.GetPublicKey()) Then + Throw New Exception("NO es válida la firma del certificado de:" & vbCrLf & + cert.SubjectDN.ToString()) + End If + + '======== Lectura de la URL del Responder ======== + + If usarUrlResponderDeCert Then + infoRev.UrlResponder = ExtraerUrlResponder(cert) + End If + + If infoRev.UrlResponder = "" Then + If urlResponder = "" Then + Throw New Exception("No se dispone de la URL del Responder OCSP.") + End If + infoRev.UrlResponder = urlResponder + End If + + '======= Lectura de arrayPadresResponderAut ======= + + Dim arrayPadresResponderAut As BC.X509.X509Certificate() + + If arrayPadresResponderAutBin Is Nothing Then + arrayPadresResponderAut = {certPadre} + Else + arrayPadresResponderAut = LeerArrayCerts(arrayPadresResponderAutBin, "arrayPadresResponderAutBin") + End If + + '=========== Lectura de arrayResponders =========== + + Dim arrayResponders As BC.X509.X509Certificate() + + If arrayRespondersBin Is Nothing Then + arrayResponders = {certPadre} + Else + arrayResponders = LeerArrayCerts(arrayRespondersBin, "arrayRespondersBin") + End If + + '============= Lectura de Requestor ============= + + Dim firmarPeticion As Boolean = (requestorBin IsNot Nothing) + + Dim requestor As BC.X509.X509Certificate + Dim chainRequestor As BC.X509.X509Certificate() + Dim privadaRequestorBC As BC.Crypto.Parameters.RsaKeyParameters + + If firmarPeticion Then + + requestor = LeerCert(requestorBin, "requestorBin") + + '------ + + If (chainRequestorBin Is Nothing) OrElse (chainRequestorBin.Length = 0) Then + + ' No se aporta la cadena de certificación del Requestor para el campo OPCIONAL 'certs' + ' del Sequence ASN.1 'Signature' de la petición OCSP: + chainRequestor = Nothing + + Else + + ' 'chainRequestorBin' debe tener el ORDEN CORRECTO para la petición OCSP: + chainRequestor = LeerArrayCerts(chainRequestorBin, "chainRequestorBin") + + End If + + '------ + + If (privadaRequestor.Modulus Is Nothing) OrElse (privadaRequestor.Modulus.Length = 0) Then + Throw New Exception("'privadaRequestor' no contiene Modulus RSA.") + End If + + If (privadaRequestor.D Is Nothing) OrElse (privadaRequestor.D.Length = 0) Then + Throw New Exception("'privadaRequestor' no contiene clave privada RSA.") + End If + + Dim modulusRequestor As New BC.Math.BigInteger(+1, privadaRequestor.Modulus) + Dim privateExponentRequestor As New BC.Math.BigInteger(+1, privadaRequestor.D) + + privadaRequestorBC = New BC.Crypto.Parameters.RsaKeyParameters(True, modulusRequestor, privateExponentRequestor) + + End If + + '========== Generación de petición OCSP ========== + + Dim reqGen As New BC.Ocsp.OcspReqGenerator() + + Dim certIDpet As New BC.Ocsp.CertificateID(BC.Ocsp.CertificateID.HashSha1, certPadre, cert.SerialNumber) + reqGen.AddRequest(certIDpet) + + Dim pet As BC.Ocsp.OcspReq + If firmarPeticion Then + reqGen.SetRequestorName(requestor.SubjectDN) + pet = reqGen.Generate("SHA256WITHRSA", privadaRequestorBC, chainRequestor) + Else + pet = reqGen.Generate() + End If + + Dim petBin As Byte() = pet.GetEncoded() + + Try + + '======== Envío de petición OCSP vía HTTP POST ======== + + reintentable = True + + Dim httpReq As Net.HttpWebRequest = CType(Net.WebRequest.Create(infoRev.UrlResponder), Net.HttpWebRequest) + + httpReq.Timeout = timeoutSegundos * 1000 ' Milisegundos + httpReq.Method = "POST" + httpReq.ContentType = "application/ocsp-request" + httpReq.ContentLength = petBin.Length + + Dim reqStream As IO.Stream = httpReq.GetRequestStream() + Try + reqStream.Write(petBin, 0, petBin.Length) + Finally + ' IMPORTANTE cerrar la conexión: + reqStream.Close() + End Try + + '============ Recepción de respuesta OCSP ============ + + Dim resp As BC.Ocsp.OcspResp + + Dim httpResp As Net.HttpWebResponse = CType(httpReq.GetResponse(), Net.HttpWebResponse) + Try + + If httpResp.StatusCode <> Net.HttpStatusCode.OK Then + Throw New Exception("Error HTTP (Código=" & CInt(httpResp.StatusCode) & "): " & httpResp.StatusDescription) + End If + + Dim respStream As IO.Stream = httpResp.GetResponseStream() + resp = New BC.Ocsp.OcspResp(respStream) + + Finally + ' IMPORTANTE hacer el Close() de la respuesta http para cerrar la conexión. + ' Este Close() también cierra el 'respStream'. + httpResp.Close() + End Try + + '============== PROCESAMIENTO DE RESPUESTA OCSP ============== + + infoRev.RespuestaOCSPBin = resp.GetEncoded() + + If resp.Status = BC.Ocsp.OcspRespStatus.Successful Then + '------------------------------------------------------------------------ + ' La respuesta OCSP es EFECTIVA (es decir, debe haber respuesta FIRMADA) + '------------------------------------------------------------------------ + + reintentable = False + infoRev.RespuestaEfectiva = True + + Dim basicResp As BC.Ocsp.BasicOcspResp = CType(resp.GetResponseObject(), BC.Ocsp.BasicOcspResp) + + Dim fechaActualUTC As Date = DateTime.UtcNow + + Dim responderUsado As BC.X509.X509Certificate + Dim msjInvalidez As String + Dim firmaValida As Boolean = FirmaValidaRespuestaOCSP(basicResp, + fechaActualUTC, arrayPadresResponderAut, + usarResponderDeRespuestaOCSP, arrayResponders, + responderUsado, msjInvalidez) + + infoRev.ResponderBin = responderUsado.GetEncoded() + + If Not firmaValida Then + Throw New Exception(msjInvalidez) + End If + + '------ + + If (basicResp.Responses Is Nothing) OrElse (basicResp.Responses.Length <> 1) Then + Throw New Exception("El número de elementos del campo 'responses' del BasicOCSPResponse es distinto de 1.") + End If + + Dim singleResp As BC.Ocsp.SingleResp = basicResp.Responses(0) + + If Not singleResp.GetCertID().Equals(certIDpet) Then + Throw New Exception("El 'CertificateID' de la respuesta OCSP no coincide con el de la petición.") + End If + + '------ + + ' 'ThisUpdate' ya viene en UTC, y siempre está presente (NO es un campo opcional): + infoRev.ThisUpdateUTC = singleResp.ThisUpdate + + ' 'NextUpdate' es un campo OPCIONAL: + If singleResp.NextUpdate Is Nothing Then + ' NextUpdate NO PRESENTE: + infoRev.NextUpdateUTC = Nothing + Else + ' NextUpdate PRESENTE (ya viene en UTC): + infoRev.NextUpdateUTC = singleResp.NextUpdate.Value + End If + + '------ + + Dim certStatus As Object = singleResp.GetCertStatus() + + If certStatus Is BC.Ocsp.CertificateStatus.Good Then ' Equivale a Is Nothing + infoRev.EstadoRevoc = EstadoRevocEnum.NoRevocado + infoRev.MsjEstadoRevoc = Nothing + infoRev.FechaRevocUTC = Nothing + + If (infoRev.NextUpdateUTC <> Nothing) AndAlso (infoRev.NextUpdateUTC < fechaActualUTC) Then + Throw New Exception("[ERROR_NEXTUPDATE_ANTIGUO] Información de revocación obsoleta porque 'NextUpdateUTC' (" & + DateToString(infoRev.NextUpdateUTC) & ") es anterior a 'FechaActualUTC' (" & + DateToString(fechaActualUTC) & ").") + End If + + ElseIf TypeOf certStatus Is BC.Ocsp.RevokedStatus Then + infoRev.EstadoRevoc = EstadoRevocEnum.Revocado + Dim revInfo As BC.Ocsp.RevokedStatus = CType(certStatus, BC.Ocsp.RevokedStatus) + infoRev.MsjEstadoRevoc = If(revInfo.HasRevocationReason, RevocationReasonToString(revInfo.RevocationReason), + RevocationReasonToString(-1)) + infoRev.FechaRevocUTC = revInfo.RevocationTime ' Ya viene en UTC + + ElseIf TypeOf certStatus Is BC.Ocsp.UnknownStatus Then + infoRev.EstadoRevoc = EstadoRevocEnum.Indeterminado + infoRev.MsjEstadoRevoc = "[CERT_STATUS_UNKNOWN] Estado de revocación del certificado: DESCONOCIDO" + infoRev.FechaRevocUTC = Nothing + + Else + Throw New Exception("'certStatus' NO CATALOGADO.") + End If + + Else + '--------------------------------- + ' NO HA HABIDO RESPUESTA EFECTIVA + '--------------------------------- + + infoRev.RespuestaEfectiva = False + + Dim msjStatus As String + Select Case resp.Status + Case BC.Ocsp.OcspRespStatus.MalformedRequest + reintentable = False + msjStatus = "Petición OCSP mal formada" + + Case BC.Ocsp.OcspRespStatus.InternalError + reintentable = True + msjStatus = "Error interno del servidor OCSP" + + Case BC.Ocsp.OcspRespStatus.TryLater + reintentable = True + msjStatus = "Servidor OCSP indica que se reintente más tarde" + + Case BC.Ocsp.OcspRespStatus.SigRequired + reintentable = False + msjStatus = "Servidor OCSP exige petición firmada" + + Case BC.Ocsp.OcspRespStatus.Unauthorized + reintentable = False + msjStatus = "Petición OCSP sin autorización" + + Case Else + reintentable = False + msjStatus = "Código de status OCSP no catalogado" + End Select + + Throw New Exception("Código de status de la respuesta OCSP: " & resp.Status & " (" & msjStatus & ")") + + End If + + Catch ex As Exception + infoRev.EstadoRevoc = EstadoRevocEnum.Indeterminado + infoRev.MsjEstadoRevoc = "[ERROR_COMUNICACION_OCSP] Error de comunicación con Servidor OCSP." & vbCrLf & ex.Message + infoRev.FechaRevocUTC = Nothing + End Try + + + Catch ex As Exception + reintentable = False + infoRev = Nothing + Throw New Exception("Error ejecutando ConsultarOCSP." & vbCrLf & ex.Message, ex) + End Try + + End Sub + + + Private Shared Function LeerCert(ByVal certBin As Byte(), + ByVal msjExcepcion As String) As BC.X509.X509Certificate + + Try + + If certBin Is Nothing Then + Throw New Exception("El certificado es Nothing.") + End If + + Dim parser As New BC.X509.X509CertificateParser() + + Return parser.ReadCertificate(certBin) + + Catch ex As Exception + Throw New Exception("Leyendo certificado (" & msjExcepcion & ")." & vbCrLf & ex.Message, ex) + End Try + + End Function + + Private Shared Function LeerArrayCerts(ByVal arrayCertsBin As Byte()(), + ByVal msjExcepcion As String) As BC.X509.X509Certificate() + + Try + + If arrayCertsBin Is Nothing Then Return Nothing + If arrayCertsBin.Length = 0 Then Return {} + + Dim parser As New BC.X509.X509CertificateParser() + + Dim arrayCerts(arrayCertsBin.Length - 1) As BC.X509.X509Certificate + + Dim i As Integer + For i = 0 To (arrayCertsBin.Length - 1) + + If arrayCertsBin(i) Is Nothing Then + Throw New Exception("El array contiene un elemento Nothing.") + End If + + arrayCerts(i) = parser.ReadCertificate(arrayCertsBin(i)) + + Next + + Return arrayCerts + + Catch ex As Exception + Throw New Exception("Leyendo array de certificados (" & msjExcepcion & ")." & vbCrLf & ex.Message, ex) + End Try + + End Function + + + Private Shared Function BuscarCertPadre(ByVal cert As BC.X509.X509Certificate, + ByVal arrayCertsPadres As BC.X509.X509Certificate()) As Integer + + '-------------------------------------------------------------- + ' RESULTADO: Índice del padre de 'cert' en 'arrayCertsPadres', + ' o -1 si no se encuentra. + '-------------------------------------------------------------- + ' Se comprueban DNs y FIRMA CRIPTOGRÁFICA. + '-------------------------------------------------------------- + + Try + + If cert Is Nothing Then Throw New Exception("'cert' es Nothing.") + + If (arrayCertsPadres Is Nothing) OrElse (arrayCertsPadres.Length = 0) Then Return -1 + + '--------- + + Dim i As Integer + For i = 0 To (arrayCertsPadres.Length - 1) + + If arrayCertsPadres(i) Is Nothing Then + Throw New Exception("'arrayCertsPadres' contiene un elemento Nothing.") + End If + + If cert.IssuerDN.Equivalent(arrayCertsPadres(i).SubjectDN, True) Then + + ' Comprobamos que cert sea hijo de arrayCertsPadres(i) CRIPTOGRÁFICAMENTE (y no sólo nominalmente): + If FirmaValidaCert(cert, arrayCertsPadres(i).GetPublicKey()) Then Return i + + End If + + Next + + Return -1 + + Catch ex As Exception + Throw New Exception("Buscando certificado emisor." & vbCrLf & ex.Message, ex) + End Try + + End Function + + + Private Shared Function ExtraerUrlResponder(ByVal cert As BC.X509.X509Certificate) As String + + '------------------------------------------------------------------------------------ + ' Devuelve la URL del Responder OCSP almacenada en la extensión AuthorityInfoAccess, + ' o Nothing si no se encuentra o si no ha sido posible su extracción. + '------------------------------------------------------------------------------------ + + Try + + If cert Is Nothing Then + Throw New Exception("El certificado es Nothing.") + End If + + ' OID de la extensión AuthorityInfoAccess (AIA): + Dim oidAIA As String = "1.3.6.1.5.5.7.1.1" + + Dim octetStrAIA As BC.Asn1.Asn1OctetString = cert.GetExtensionValue(New BC.Asn1.DerObjectIdentifier(oidAIA)) + + If octetStrAIA Is Nothing Then Return Nothing + + Dim asn1objAIA As BC.Asn1.Asn1Object + Try + ' Parsing ASN.1 del valor de la extensión AIA: + asn1objAIA = BC.Asn1.Asn1Object.FromByteArray(octetStrAIA.GetOctets()) + Catch ex As Exception + Return Nothing + End Try + + ' NOTA: (TypeOf Nothing Is ) = False + If Not (TypeOf asn1objAIA Is BC.Asn1.Asn1Sequence) Then Return Nothing + + Dim seqAIA As BC.Asn1.Asn1Sequence = CType(asn1objAIA, BC.Asn1.Asn1Sequence) + + Dim oidAccessMethodOCSP As String = "1.3.6.1.5.5.7.48.1" + + Dim seqAccessDescription As BC.Asn1.Asn1Sequence + Dim oidAccessMethod As String + Dim gnAccessLocation As BC.Asn1.X509.GeneralName + + Dim i As Integer + For i = 0 To (seqAIA.Count - 1) + + If Not (TypeOf seqAIA(i) Is BC.Asn1.Asn1Sequence) Then Continue For + + seqAccessDescription = CType(seqAIA(i), BC.Asn1.Asn1Sequence) + + If seqAccessDescription.Count <> 2 Then Continue For + + If Not (TypeOf seqAccessDescription(0) Is BC.Asn1.DerObjectIdentifier) Then Continue For + + oidAccessMethod = CType(seqAccessDescription(0), BC.Asn1.DerObjectIdentifier).Id + + If oidAccessMethod <> oidAccessMethodOCSP Then Continue For + + Try + gnAccessLocation = BC.Asn1.X509.GeneralName.GetInstance(seqAccessDescription(1)) + Catch ex As Exception + Continue For + End Try + + If gnAccessLocation Is Nothing Then Continue For + + If gnAccessLocation.TagNo <> BC.Asn1.X509.GeneralName.UniformResourceIdentifier Then Continue For + + If Not (TypeOf gnAccessLocation.Name Is BC.Asn1.DerIA5String) Then Continue For + + Return CType(gnAccessLocation.Name, BC.Asn1.DerIA5String).GetString() + + Next + + Return Nothing + + Catch ex As Exception + Throw New Exception("Extrayendo URL del Responder OCSP." & vbCrLf & ex.Message, ex) + End Try + + End Function + + + Private Shared Function TieneOCSPSigning(ByVal cert As BC.X509.X509Certificate) As Boolean + + '------------------------------------------------------------------------------------ + ' Devuelve True si 'cert' tiene el OID OCSPSigning en la extensión ExtendedKeyUsage. + '------------------------------------------------------------------------------------ + + Try + + If cert Is Nothing Then + Throw New Exception("El certificado es Nothing.") + End If + + ' Lista de OIDs (KeyPurposeIds) como STRINGS: + Dim oids As IList = cert.GetExtendedKeyUsage() + + If oids Is Nothing Then Return False + + Dim oidOCSPSigning As String = "1.3.6.1.5.5.7.3.9" + + Dim i As Integer + For i = 0 To (oids.Count - 1) + If CType(oids(i), String) = oidOCSPSigning Then Return True + Next + + Return False + + Catch ex As Exception + Throw New Exception("Comprobando presencia de OCSPSigning en la extensión ExtendedKeyUsage." & vbCrLf & ex.Message, ex) + End Try + + End Function + + Private Shared Function BuscarResponderAutorizado(ByVal certs As BC.X509.X509Certificate()) As BC.X509.X509Certificate + + '--------------------------------------------------------------------- + ' Devuelve la primera aparición en 'certs' de un Responder Autorizado + ' (es decir, con OCSPSigning), o Nothing si no se encuentra. + '--------------------------------------------------------------------- + + Try + + If certs Is Nothing Then Return Nothing + + Dim i As Integer + For i = 0 To (certs.Length - 1) + If (certs(i) IsNot Nothing) AndAlso TieneOCSPSigning(certs(i)) Then Return certs(i) + Next + + Return Nothing + + Catch ex As Exception + Throw New Exception("Buscando Responder Autorizado." & vbCrLf & ex.Message, ex) + End Try + + End Function + + + Private Shared Function FirmaValidaCert(ByVal cert As BC.X509.X509Certificate, + ByVal clavePublica As BC.Crypto.AsymmetricKeyParameter) As Boolean + + '----------------------------------------------- + ' Sólo se comprueba la firma digital de 'cert'. + '----------------------------------------------- + + Try + + If cert Is Nothing Then Throw New Exception("'cert' es Nothing.") + If clavePublica Is Nothing Then Throw New Exception("'clavePublica' es Nothing.") + + Try + + ' 'Verify' comprueba la firma digital. Es un Sub que eleva EXCEPCIÓN si la firma es incorrecta: + cert.Verify(clavePublica) + + Catch ex As Exception + ' La firma NO es válida: + Return False + End Try + + ' Si llegamos aquí es porque la firma es válida: + Return True + + Catch ex As Exception + Throw New Exception("Validando firma de un certificado." & vbCrLf & ex.Message, ex) + End Try + + End Function + + Private Shared Function ValidoCertSinRevoc(ByVal cert As BC.X509.X509Certificate, + ByVal arrayCertsPadres As BC.X509.X509Certificate(), + ByVal fechaUTC As Date, + Optional ByRef msjInvalidez As String = Nothing) As Boolean + + '-------------------------------------------------------------------- + ' No se comprueba el estado de revocación de 'cert'. + ' 'arrayCertsPadres' contiene los posibles padres (al menos 1). + ' Se asume que los padres han sido VALIDADOS PREVIAMENTE. + '-------------------------------------------------------------------- + ' msjInvalidez: Nothing si 'cert' es VÁLIDO según algún padre; + ' mensaje con la causa de la invalidez si es INVÁLIDO. + '-------------------------------------------------------------------- + + Try + + If cert Is Nothing Then Throw New Exception("'cert' es Nothing.") + + If (arrayCertsPadres Is Nothing) OrElse (arrayCertsPadres.Length = 0) Then + Throw New Exception("'arrayCertsPadres' es Nothing o vacío.") + End If + + '----------- + + If arrayCertsPadres.Length = 1 Then + + If arrayCertsPadres(0) Is Nothing Then + Throw New Exception("El único elemento de 'arrayCertsPadres' es Nothing.") + End If + + Dim certPadre As BC.X509.X509Certificate = arrayCertsPadres(0) + + msjInvalidez = "Validando certificado de: " & cert.SubjectDN.ToString() & vbCrLf & + " respecto a: " & certPadre.SubjectDN.ToString() & vbCrLf + + If Not cert.IssuerDN.Equivalent(certPadre.SubjectDN, True) Then + msjInvalidez &= "El segundo certificado no es el emisor del primero." + Return False + End If + + If Not FirmaValidaCert(cert, certPadre.GetPublicKey()) Then + msjInvalidez &= "Firma NO válida." + Return False + End If + + Else + + ' La búsqueda siguiente comprueba DNs y firma criptográfica: + Dim iPadre As Integer = BuscarCertPadre(cert, arrayCertsPadres) + + If iPadre = -1 Then + msjInvalidez = "Validando certificado de: " & cert.SubjectDN.ToString() & vbCrLf & + "respecto a " & arrayCertsPadres.Length & " certificados emisores posibles." & vbCrLf & + "No se ha encontrado certificado emisor." + Return False + End If + + msjInvalidez = "Validando certificado de: " & cert.SubjectDN.ToString() & vbCrLf & + " respecto a: " & arrayCertsPadres(iPadre).SubjectDN.ToString() & vbCrLf + + End If + + '----------- + + ' NOTA: cert.NotBefore y cert.NotAfter están en UTC + + If fechaUTC < cert.NotBefore Then + msjInvalidez &= "Certificado aún no válido en " & DateToString(fechaUTC.ToLocalTime()) & + " (válido desde " & DateToString(cert.NotBefore.ToLocalTime()) & ")." + Return False + End If + + If fechaUTC > cert.NotAfter Then + msjInvalidez &= "Certificado caducado en " & DateToString(fechaUTC.ToLocalTime()) & + " (válido hasta " & DateToString(cert.NotAfter.ToLocalTime()) & ")." + Return False + End If + + '----------- + + msjInvalidez = Nothing + Return True + + Catch ex As Exception + msjInvalidez = Nothing + Throw New Exception("Validando certificado." & vbCrLf & ex.Message, ex) + End Try + + End Function + + + Private Shared Function FirmaValidaRespuestaOCSP(ByVal basicResp As BC.Ocsp.BasicOcspResp, + ByVal fechaUTC As Date, + ByVal arrayPadresResponderAut As BC.X509.X509Certificate(), + ByVal usarResponderDeRespuestaOCSP As Boolean, + ByVal arrayResponders As BC.X509.X509Certificate(), + Optional ByRef responderUsado As BC.X509.X509Certificate = Nothing, + Optional ByRef msjInvalidez As String = Nothing) As Boolean + + '---------------------------------------------------------------------------------------------------------- + ' No se tiene en cuenta (ThisUpdate, NextUpdate) de la respuesta OCSP. + '---------------------------------------------------------------------------------------------------------- + ' fechaUTC: Fecha en la que se validará el Responder Autorizado contenido en la respuesta OCSP. + ' + ' arrayPadresResponderAut: Padres permitidos para el Responder Autorizado. + ' Se usarán para validar el Responder Autorizado contenido en la respuesta OCSP. + ' Se asume que los padres han sido VALIDADOS PREVIAMENTE. + ' Debe ser no vacío si usarResponderDeRespuestaOCSP = True. + ' El valor Nothing NO tiene ningún significado especial. + ' + ' usarResponderDeRespuestaOCSP: True si se desea usar el Responder Autorizado contenido en la respuesta OCSP + ' (en el campo 'certs' del Sequence ASN.1 'BasicOCSPResponse'). + ' + ' arrayResponders: Responders locales (se asume que han sido VALIDADOS PREVIAMENTE) que se usarán para + ' validar la firma de la respuesta OCSP en el caso de que ésta no contenga un Responder + ' Autorizado, o cuando usarResponderDeRespuestaOCSP = False. + ' Si la respuesta contiene un Responder Autorizado y usarResponderDeRespuestaOCSP = True, + ' entonces NO se usarán los Responders locales pasados como argumento. + ' El valor Nothing NO tiene ningún significado especial. + '---------------------------------------------------------------------------------------------------------- + ' responderUsado: Se devuelve el certificado Responder usado para validar la firma de la respuesta OCSP. + ' Siempre es distinto de Nothing, salvo cuando hay excepción. + ' + ' msjInvalidez: Nothing si la firma de la respuesta OCSP es VÁLIDA; + ' mensaje con la causa de la invalidez si es INVÁLIDA. + '---------------------------------------------------------------------------------------------------------- + + Try + + If basicResp Is Nothing Then + Throw New Exception("'basicResp' es Nothing.") + End If + + If usarResponderDeRespuestaOCSP AndAlso + ((arrayPadresResponderAut Is Nothing) OrElse (arrayPadresResponderAut.Length = 0)) Then + Throw New Exception("'usarResponderDeRespuestaOCSP' es True y 'arrayPadresResponderAut' es Nothing o vacío.") + End If + + '----------- + + msjInvalidez = "Validando firma de la respuesta OCSP." & vbCrLf + + If usarResponderDeRespuestaOCSP Then + + Dim responderAut As BC.X509.X509Certificate = BuscarResponderAutorizado(basicResp.GetCerts()) + + If responderAut IsNot Nothing Then + ' IMPORTANTE: 'responderAut' tiene OCSPSigning en la extensión ExtendedKeyUsage. + + Dim msjInvalidezResponderAut As String + If Not ValidoCertSinRevoc(responderAut, arrayPadresResponderAut, fechaUTC, msjInvalidezResponderAut) Then + responderUsado = responderAut + msjInvalidez &= "Validando Responder contenido en la respuesta OCSP." & vbCrLf & + msjInvalidezResponderAut + Return False + End If + + ' Sustituimos los Responders pasados como argumento con el Responder Autorizado: + arrayResponders = {responderAut} + + End If + + End If + + If (arrayResponders Is Nothing) OrElse (arrayResponders.Length = 0) Then + Throw New Exception("No se dispone de certificado Responder.") + End If + + Dim i As Integer + For i = 0 To (arrayResponders.Length - 1) + + If arrayResponders(i) Is Nothing Then + Throw New Exception("'arrayResponders' contiene un elemento Nothing.") + End If + + responderUsado = arrayResponders(i) + + If basicResp.Verify(responderUsado.GetPublicKey()) Then + msjInvalidez = Nothing + Return True + End If + + Next + + msjInvalidez &= "Firma NO válida." + Return False + + Catch ex As Exception + responderUsado = Nothing + msjInvalidez = Nothing + Throw New Exception("Validando firma de la respuesta OCSP." & vbCrLf & ex.Message, ex) + End Try + + End Function + + + Private Shared Function CertCorrespondeARespuestaOCSP(ByVal cert As BC.X509.X509Certificate, + ByVal arrayCertsPadres As BC.X509.X509Certificate(), + ByVal basicResp As BC.Ocsp.BasicOcspResp) As Boolean + + '---------------------------------------------------------------------------------------- + ' Devuelve True si el certificado 'cert' corresponde a la respuesta OCSP, es decir, + ' si la información de revocación contenida en 'basicResp' es de 'cert'. + '---------------------------------------------------------------------------------------- + ' Lo que se comprueba es la coincidencia del CertificateID de 'cert' con el + ' CertificateID contenido en la respuesta OCSP. + '---------------------------------------------------------------------------------------- + ' arrayCertsPadres: Array con los posibles padres de 'cert' (al menos 1). + ' Se asume que los certificados han sido VALIDADOS PREVIAMENTE. + ' La función busca el padre correspondiente. + '---------------------------------------------------------------------------------------- + + Try + + If cert Is Nothing Then + Throw New Exception("'cert' es Nothing.") + End If + + If (arrayCertsPadres Is Nothing) OrElse (arrayCertsPadres.Length = 0) Then + Throw New Exception("'arrayCertsPadres' es Nothing o vacío.") + End If + + If basicResp Is Nothing Then + Throw New Exception("'basicResp' es Nothing.") + End If + + '============ + + If (basicResp.Responses Is Nothing) OrElse (basicResp.Responses.Length <> 1) Then + Throw New Exception("El número de elementos del campo 'responses' del BasicOCSPResponse es distinto de 1.") + End If + + Dim certIDResp As BC.Ocsp.CertificateID = basicResp.Responses(0).GetCertID() + + '============ + + Dim iPadre As Integer = BuscarCertPadre(cert, arrayCertsPadres) + + If iPadre = -1 Then + Throw New Exception("No se ha encontrado certificado emisor.") + End If + + Dim certPadre As BC.X509.X509Certificate = arrayCertsPadres(iPadre) + + Dim certID As New BC.Ocsp.CertificateID(BC.Ocsp.CertificateID.HashSha1, certPadre, cert.SerialNumber) + + '============ + + Return certID.Equals(certIDResp) + + Catch ex As Exception + Throw New Exception("Comprobando correspondencia del certificado con la respuesta OCSP." & vbCrLf & ex.Message, ex) + End Try + + End Function + +#End Region + +End Class diff --git a/TSPdfUtils/pdf.vb b/TSPdfUtils/pdf.vb new file mode 100644 index 0000000..ec140b7 --- /dev/null +++ b/TSPdfUtils/pdf.vb @@ -0,0 +1,743 @@ + +Imports System.IO +Imports TSpdf.Kernel.Font + +Imports System.Security.Cryptography.X509Certificates + +Imports System.Security.Cryptography +Imports TSpdf.Kernel.Pdf +Imports TSpdf.Kernel.Utils +Imports TSpdf.Forms +Imports TSpdf.Signatures +Imports System.Text +Imports TSpdf.Kernel.Pdf.Action +Imports TSpdf.Kernel.Pdf.Navigation +Imports TSpdf.Kernel.Pdf.Canvas +Imports TSpdf.Kernel.Geom +Imports TSpdf.Layout.Element +Imports TSpdf.IO.Font.Constants +Imports System.Diagnostics.Contracts +Imports System.Text.RegularExpressions +Imports Microsoft.VisualBasic.Devices +Imports Org.BouncyCastle.Asn1 +Imports Org.BouncyCastle.Pkcs +Imports TSpdf.Commons.Bouncycastle.Cert +Imports TSpdf.Commons.Bouncycastle.Crypto +Imports TSpdf.Bouncycastle.Crypto +Imports TSpdf.Bouncycastle.Cert +Imports Org.BouncyCastle.Asn1.X509 +Imports Org.BouncyCastle.X509 +Imports Org.BouncyCastle.Crypto.Tls +Imports TSpdf +Imports TSpdf.Kernel.Pdf.Canvas.Parser +Imports TSpdf.Kernel.Pdf.Canvas.Parser.Listener + +Public Class pdf + Public Shared Sub Aplanar(Origen As String) + Dim PdfDocument = New PdfDocument(New PdfReader(Origen)) + Dim form = PdfAcroForm.GetAcroForm(PdfDocument, False) + If form IsNot Nothing Then form.FlattenFields() + PdfDocument.Close() + End Sub + + Public Shared Sub Aplanar(Origen As String, Destino As String) + Dim PdfDocument = New PdfDocument(New PdfReader(Origen), New PdfWriter(Destino)) + Dim form = PdfAcroForm.GetAcroForm(PdfDocument, False) + If form IsNot Nothing Then form.FlattenFields() + PdfDocument.Close() + End Sub + + Public Shared Function ObtieneFicheroAleatorio(ByVal Extension As String) As String + Dim sFichero As String = System.IO.Path.GetTempPath & System.IO.Path.GetRandomFileName & "." & Extension + Do While System.IO.File.Exists(sFichero) + 'Try + ' IO.File.Delete(sFichero) + 'Catch ex As Exception + sFichero = System.IO.Path.GetTempPath & "\" & System.IO.Path.GetRandomFileName & "." & Extension + ' End Try + Loop + Return sFichero + End Function + + Public Shared Sub UnePdfs(Pdfs()() As Byte, outputPdf As String, Optional UnirTags As Boolean = True, Optional UnirMarcadores As Boolean = True, Optional MarcadoresInicioFichero() As String = Nothing, Optional Aplanar As Boolean = True) + Dim lista = Pdfs.Select(Function(x) New MemoryStream(x)).ToArray + UnePdfs(lista, outputPdf, UnirTags, UnirMarcadores, MarcadoresInicioFichero, Aplanar) + End Sub + + Public Shared Sub UnePdfs(Pdfs() As String, st As Stream, Optional UnirTags As Boolean = True, Optional UnirMarcadores As Boolean = True, Optional MarcadoresInicioFichero() As String = Nothing, Optional Aplanar As Boolean = True) + Try + Dim pdfw As New PdfWriter(st) + Dim pdfDoc As PdfDocument = New PdfDocument(pdfw) + Dim merger As PdfMerger = New PdfMerger(pdfDoc, UnirTags, UnirMarcadores) + Dim i As Integer = 0 + Dim NumeroPaginasTotal As Integer = 1 + Dim rootoutline As PdfOutline = Nothing + Dim pagini(Pdfs.Count - 1) As Integer + For Each pdf In Pdfs + i += 1 + Dim pdfr As New PdfReader(pdf) + pdfr.SetUnethicalReading(True) + Dim ms As New MemoryStream + Dim pdfwtmp As New PdfWriter(ms) + Dim srcDoc As PdfDocument + + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores Then + srcDoc = New PdfDocument(pdfr, pdfwtmp) + MueveMarcadorARaiz(srcDoc, MarcadoresInicioFichero(i - 1)) + srcDoc.Close() + pdfr = New PdfReader(New MemoryStream(ms.ToArray)) + srcDoc = New PdfDocument(pdfr) + Else + srcDoc = New PdfDocument(pdfr) + End If + Dim np = srcDoc.GetNumberOfPages + pagini(i - 1) = np + If Aplanar Then + Dim form = PdfAcroForm.GetAcroForm(srcDoc, False) + If form IsNot Nothing Then form.FlattenFields() + End If + merger.SetCloseSourceDocuments(i = Pdfs.Count).Merge(srcDoc, 1, np) + srcDoc.Close() + Next + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores = False Then + pdfDoc.InitializeOutlines() + rootoutline = pdfDoc.GetOutlines(False) + For i = 0 To pagini.Length - 1 + Dim oul As PdfOutline = rootoutline.AddOutline(MarcadoresInicioFichero(i)) + oul.AddDestination(PdfExplicitDestination.CreateFit(pdfDoc.GetPage(NumeroPaginasTotal))) + NumeroPaginasTotal += pagini(i) + Next + End If + pdfDoc.Close() + pdfw.Close() + merger.Close() + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + Public Shared Sub UnePdfs(Pdfs() As String, outputPdf As String, Optional UnirTags As Boolean = True, Optional UnirMarcadores As Boolean = True, Optional MarcadoresInicioFichero() As String = Nothing, Optional Aplanar As Boolean = True) + Try + Dim pdfw As New PdfWriter(outputPdf) + Dim pdfDoc As PdfDocument = New PdfDocument(pdfw) + Dim merger As PdfMerger = New PdfMerger(pdfDoc, UnirTags, UnirMarcadores) + Dim i As Integer = 0 + Dim NumeroPaginasTotal As Integer = 1 + Dim rootoutline As PdfOutline = Nothing + Dim pagini(Pdfs.Count - 1) As Integer + For Each pdf In Pdfs + i += 1 + Dim pdfr As New PdfReader(pdf) + pdfr.SetUnethicalReading(True) + Dim ms As New MemoryStream + Dim pdfwtmp As New PdfWriter(ms) + Dim srcDoc As PdfDocument + + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores Then + srcDoc = New PdfDocument(pdfr, pdfwtmp) + MueveMarcadorARaiz(srcDoc, MarcadoresInicioFichero(i - 1)) + srcDoc.Close() + pdfr = New PdfReader(New MemoryStream(ms.ToArray)) + srcDoc = New PdfDocument(pdfr) + Else + srcDoc = New PdfDocument(pdfr) + End If + Dim np = srcDoc.GetNumberOfPages + pagini(i - 1) = np + If Aplanar Then + Dim form = PdfAcroForm.GetAcroForm(srcDoc, False) + If form IsNot Nothing Then form.FlattenFields() + End If + merger.SetCloseSourceDocuments(i = Pdfs.Count).Merge(srcDoc, 1, np) + srcDoc.Close() + Next + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores = False Then + pdfDoc.InitializeOutlines() + rootoutline = pdfDoc.GetOutlines(False) + For i = 0 To pagini.Length - 1 + Dim oul As PdfOutline = rootoutline.AddOutline(MarcadoresInicioFichero(i)) + oul.AddDestination(PdfExplicitDestination.CreateFit(pdfDoc.GetPage(NumeroPaginasTotal))) + NumeroPaginasTotal += pagini(i) + Next + End If + pdfDoc.Close() + pdfw.Close() + merger.Close() + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + Public Shared Sub UnePdfs(Pdfs() As Stream, outputPdf As String, Optional UnirTags As Boolean = True, Optional UnirMarcadores As Boolean = True, Optional MarcadoresInicioFichero() As String = Nothing, Optional Aplanar As Boolean = True) + Try + Dim pdfw As New PdfWriter(outputPdf) + Dim pdfDoc As PdfDocument = New PdfDocument(pdfw) + Dim merger As PdfMerger = New PdfMerger(pdfDoc, UnirTags, UnirMarcadores) + Dim i As Integer = 0 + Dim NumeroPaginasTotal As Integer = 1 + Dim rootoutline As PdfOutline = Nothing + Dim pagini(Pdfs.Count - 1) As Integer + For Each pdf In Pdfs + i += 1 + Dim pdfr As New PdfReader(pdf) + pdfr.SetUnethicalReading(True) + Dim ms As New MemoryStream + Dim pdfwtmp As New PdfWriter(ms) + Dim srcDoc As PdfDocument + + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores Then + srcDoc = New PdfDocument(pdfr, pdfwtmp) + MueveMarcadorARaiz(srcDoc, MarcadoresInicioFichero(i - 1)) + srcDoc.Close() + pdfr = New PdfReader(New MemoryStream(ms.ToArray)) + srcDoc = New PdfDocument(pdfr) + Else + srcDoc = New PdfDocument(pdfr) + End If + Dim np = srcDoc.GetNumberOfPages + pagini(i - 1) = np + If Aplanar Then + Dim form = PdfAcroForm.GetAcroForm(srcDoc, False) + If form IsNot Nothing Then form.FlattenFields() + End If + merger.SetCloseSourceDocuments(i = Pdfs.Count).Merge(srcDoc, 1, np) + srcDoc.Close() + Next + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores = False Then + pdfDoc.InitializeOutlines() + rootoutline = pdfDoc.GetOutlines(False) + For i = 0 To pagini.Length - 1 + Dim oul As PdfOutline = rootoutline.AddOutline(MarcadoresInicioFichero(i)) + oul.AddDestination(PdfExplicitDestination.CreateFit(pdfDoc.GetPage(NumeroPaginasTotal))) + NumeroPaginasTotal += pagini(i) + Next + End If + pdfDoc.Close() + pdfw.Close() + merger.Close() + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + + + + End Sub + Public Shared Function UnePdfs(Pdfs()() As Byte, Optional UnirTags As Boolean = True, Optional UnirMarcadores As Boolean = True, Optional MarcadoresInicioFichero() As String = Nothing, Optional Aplanar As Boolean = True) As Byte() + Dim lista = Pdfs.Select(Function(x) New MemoryStream(x)).ToArray + Return UnePdfs(lista, UnirTags, UnirMarcadores, MarcadoresInicioFichero, Aplanar) + End Function + Public Shared Function UnePdfs(Pdfs() As Stream, Optional UnirTags As Boolean = True, Optional UnirMarcadores As Boolean = True, Optional MarcadoresInicioFichero() As String = Nothing, Optional Aplanar As Boolean = True) As Byte() + Try + Dim msw As New MemoryStream + Dim pdfw As New PdfWriter(msw) + Dim pdfDoc As PdfDocument = New PdfDocument(pdfw) + Dim merger As PdfMerger = New PdfMerger(pdfDoc, UnirTags, UnirMarcadores) + Dim i As Integer = 0 + Dim NumeroPaginasTotal As Integer = 1 + Dim rootoutline As PdfOutline = Nothing + Dim pagini(Pdfs.Count - 1) As Integer + For Each pdf In Pdfs + i += 1 + Dim pdfr As New PdfReader(pdf) + pdfr.SetUnethicalReading(True) + Dim ms As New MemoryStream + Dim pdfwtmp As New PdfWriter(ms) + Dim srcDoc As PdfDocument + + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores Then + srcDoc = New PdfDocument(pdfr, pdfwtmp) + MueveMarcadorARaiz(srcDoc, MarcadoresInicioFichero(i - 1)) + srcDoc.Close() + pdfr = New PdfReader(New MemoryStream(ms.ToArray)) + srcDoc = New PdfDocument(pdfr) + Else + srcDoc = New PdfDocument(pdfr) + End If + Dim np = srcDoc.GetNumberOfPages + pagini(i - 1) = np + If Aplanar Then + Dim form = PdfAcroForm.GetAcroForm(srcDoc, False) + If form IsNot Nothing Then form.FlattenFields() + End If + merger.SetCloseSourceDocuments(i = Pdfs.Count).Merge(srcDoc, 1, np) + srcDoc.Close() + Next + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores = False Then + pdfDoc.InitializeOutlines() + rootoutline = pdfDoc.GetOutlines(False) + For i = 0 To pagini.Length - 1 + Dim oul As PdfOutline = rootoutline.AddOutline(MarcadoresInicioFichero(i)) + oul.AddDestination(PdfExplicitDestination.CreateFit(pdfDoc.GetPage(NumeroPaginasTotal))) + NumeroPaginasTotal += pagini(i) + Next + End If + pdfDoc.Close() + pdfw.Close() + merger.Close() + Return msw.ToArray + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Function + + Public Shared Sub UnePdfs(Pdfs() As Stream, Destino As Stream, Optional UnirTags As Boolean = True, Optional UnirMarcadores As Boolean = True, Optional MarcadoresInicioFichero() As String = Nothing, Optional Aplanar As Boolean = True) + Try + Dim pdfw As New PdfWriter(Destino) + Dim pdfDoc As PdfDocument = New PdfDocument(pdfw) + Dim merger As PdfMerger = New PdfMerger(pdfDoc, UnirTags, UnirMarcadores) + Dim i As Integer = 0 + Dim NumeroPaginasTotal As Integer = 1 + Dim rootoutline As PdfOutline = Nothing + Dim pagini(Pdfs.Count - 1) As Integer + For Each pdf In Pdfs + i += 1 + Dim pdfr As New PdfReader(pdf) + pdfr.SetUnethicalReading(True) + Dim ms As New MemoryStream + Dim pdfwtmp As New PdfWriter(ms) + Dim srcDoc As PdfDocument + + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores Then + srcDoc = New PdfDocument(pdfr, pdfwtmp) + MueveMarcadorARaiz(srcDoc, MarcadoresInicioFichero(i - 1)) + srcDoc.Close() + pdfr = New PdfReader(New MemoryStream(ms.ToArray)) + srcDoc = New PdfDocument(pdfr) + Else + srcDoc = New PdfDocument(pdfr) + End If + Dim np = srcDoc.GetNumberOfPages + pagini(i - 1) = np + If Aplanar Then + Dim form = PdfAcroForm.GetAcroForm(srcDoc, False) + If form IsNot Nothing Then form.FlattenFields() + End If + merger.SetCloseSourceDocuments(i = Pdfs.Count).Merge(srcDoc, 1, np) + srcDoc.Close() + Next + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores = False Then + pdfDoc.InitializeOutlines() + rootoutline = pdfDoc.GetOutlines(False) + For i = 0 To pagini.Length - 1 + Dim oul As PdfOutline = rootoutline.AddOutline(MarcadoresInicioFichero(i)) + oul.AddDestination(PdfExplicitDestination.CreateFit(pdfDoc.GetPage(NumeroPaginasTotal))) + NumeroPaginasTotal += pagini(i) + Next + End If + pdfDoc.Close() + pdfw.Close() + merger.Close() + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + Public Shared Function UnePdfs(Pdfs() As String, Optional UnirTags As Boolean = True, Optional UnirMarcadores As Boolean = True, Optional MarcadoresInicioFichero() As String = Nothing, Optional Aplanar As Boolean = True) As Byte() + Try + Dim msw As New MemoryStream + Dim pdfw As New PdfWriter(msw) + Dim pdfDoc As PdfDocument = New PdfDocument(pdfw) + Dim merger As PdfMerger = New PdfMerger(pdfDoc, UnirTags, UnirMarcadores) + Dim i As Integer = 0 + Dim NumeroPaginasTotal As Integer = 1 + Dim rootoutline As PdfOutline = Nothing + Dim pagini(Pdfs.Count - 1) As Integer + For Each pdf In Pdfs + i += 1 + Dim pdfr As New PdfReader(pdf) + pdfr.SetUnethicalReading(True) + Dim ms As New MemoryStream + Dim pdfwtmp As New PdfWriter(ms) + Dim srcDoc As PdfDocument + + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores Then + srcDoc = New PdfDocument(pdfr, pdfwtmp) + MueveMarcadorARaiz(srcDoc, MarcadoresInicioFichero(i - 1)) + srcDoc.Close() + pdfr = New PdfReader(New MemoryStream(ms.ToArray)) + srcDoc = New PdfDocument(pdfr) + Else + srcDoc = New PdfDocument(pdfr) + End If + Dim np = srcDoc.GetNumberOfPages + pagini(i - 1) = np + If Aplanar Then + Dim form = PdfAcroForm.GetAcroForm(srcDoc, False) + If form IsNot Nothing Then form.FlattenFields() + End If + merger.SetCloseSourceDocuments(i = Pdfs.Count).Merge(srcDoc, 1, np) + srcDoc.Close() + Next + If MarcadoresInicioFichero IsNot Nothing AndAlso UnirMarcadores = False Then + pdfDoc.InitializeOutlines() + rootoutline = pdfDoc.GetOutlines(False) + For i = 0 To pagini.Length - 1 + Dim oul As PdfOutline = rootoutline.AddOutline(MarcadoresInicioFichero(i)) + oul.AddDestination(PdfExplicitDestination.CreateFit(pdfDoc.GetPage(NumeroPaginasTotal))) + NumeroPaginasTotal += pagini(i) + Next + End If + pdfDoc.Close() + pdfw.Close() + merger.Close() + Return msw.ToArray + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + + End Function + + + Private Shared Sub MueveMarcadorARaiz(ByVal pdfDocument As PdfDocument, ByVal EtiquetaMarcadorRaiz As String) + pdfDocument.InitializeOutlines() + Try + Dim pp = pdfDocument.GetFirstPage() + Dim rootOutline As PdfOutline = pdfDocument.GetOutlines(False) + Dim subOutline As PdfOutline = rootOutline.AddOutline(EtiquetaMarcadorRaiz) + + + Dim dest As PdfDestination = PdfExplicitDestination.CreateFit(pp) + subOutline.AddDestination(dest) + Dim pdfOutlineChildren As List(Of PdfOutline) = rootOutline.GetAllChildren().ToList + If pdfOutlineChildren.Count = 1 Then + Return + End If + Dim i As Integer = 0 + Dim p As PdfOutline + + For Each p In pdfOutlineChildren + If Not p.Equals(subOutline) Then + Dim nd = p.GetDestination + If nd Is Nothing Then nd = dest + dest = nd + Corrigeoutl(p, dest) + subOutline.AddOutline(p) + End If + Next + rootOutline.GetAllChildren().Clear() + rootOutline.AddOutline(subOutline) + subOutline.AddDestination(PdfExplicitDestination.CreateFit(pdfDocument.GetFirstPage())) + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + Private Shared Sub CreaMarcadorInicioFichero(ByVal pdfDocument As PdfDocument, ByVal EtiquetaMarcadorRaiz As String) + pdfDocument.InitializeOutlines() + Try + Dim pp = pdfDocument.GetFirstPage() + Dim rootOutline As PdfOutline = pdfDocument.GetOutlines(False) + Dim subOutline As PdfOutline = rootOutline.AddOutline(EtiquetaMarcadorRaiz) + + + Dim dest As PdfDestination = PdfExplicitDestination.CreateFit(pp) + subOutline.AddDestination(dest) + Dim pdfOutlineChildren As List(Of PdfOutline) = rootOutline.GetAllChildren().ToList + If pdfOutlineChildren.Count = 1 Then + Return + End If + Dim i As Integer = 0 + Dim p As PdfOutline + + For Each p In pdfOutlineChildren + If Not p.Equals(subOutline) Then + Dim nd = p.GetDestination + If nd Is Nothing Then nd = dest + dest = nd + Corrigeoutl(p, dest) + subOutline.AddOutline(p) + End If + Next + rootOutline.GetAllChildren().Clear() + rootOutline.AddOutline(subOutline) + subOutline.AddDestination(PdfExplicitDestination.CreateFit(pdfDocument.GetFirstPage())) + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + Private Shared Sub Corrigeoutl(p As PdfOutline, ByRef ultimoDestino As PdfDestination) + If p.GetDestination Is Nothing Then + p.AddDestination(ultimoDestino) + End If + Dim hijos = p.GetAllChildren + For Each h In hijos + If h.GetDestination Is Nothing OrElse h.GetDestination.GetPdfObject Is Nothing Then + h.AddDestination(ultimoDestino) + End If + ultimoDestino = h.GetDestination + Corrigeoutl(h, ultimoDestino) + Next + End Sub + + + Public Shared Function ObtieneNumeroPaginasPDF(PDF As String) As Integer + Dim pdfr As New PdfReader(PDF) + Dim NP = New PdfDocument(pdfr).GetNumberOfPages + pdfr.Close() + Return NP + End Function + + Public Shared Sub ConvierteAPDFX(PdfOrigen As String, PdfDestino As String) + Dim wp As WriterProperties = New WriterProperties() + wp.SetPdfVersion(PdfVersion.PDF_1_3) + Dim pdfDoc As PdfDocument = New PdfDocument(New PdfReader(PdfOrigen), New PdfWriter(PdfDestino, wp)) + pdfDoc.Close() + End Sub + + + Public Shared Sub RotarPdf(ByVal PdfOrigen As String, ByVal PdfDestino As String, Grados As Integer) + + Dim pdfDoc As PdfDocument = New PdfDocument(New PdfReader(PdfOrigen), New PdfWriter(PdfDestino)) + For p As Integer = 1 To pdfDoc.GetNumberOfPages() + Dim page As PdfPage = pdfDoc.GetPage(p) + Dim rotate As Integer = page.GetRotation() + If rotate = 0 Then + page.SetRotation(Grados) + Else + page.SetRotation((rotate + Grados) Mod 360) + End If + Next + pdfDoc.Close() + + End Sub + + + + + + + + Public Shared Sub RecortaPDF(ByVal PdfOrigen As String, PdfRecortado As String, PaginaInicio As Integer, PaginaFin As Integer) + Dim pdfDoc As PdfDocument = New PdfDocument(New PdfWriter(PdfRecortado)) + Dim merger As PdfMerger = New PdfMerger(pdfDoc, False, False) + Dim srcDoc As PdfDocument = New PdfDocument(New PdfReader(PdfOrigen)) + Dim np = srcDoc.GetNumberOfPages + merger.SetCloseSourceDocuments(True).Merge(srcDoc, PaginaInicio, PaginaFin) + pdfDoc.Close() + End Sub + + + + + Public Shared Sub RellenaFormularioPDF(PdfOrigen As String, PdfDestino As String, Valores As Hashtable, Optional Aplanar As Boolean = True) + Try + Dim fs As New FileStream(PdfDestino, FileMode.CreateNew, FileAccess.Write) + RellenaFormularioPDF(PdfOrigen, fs, Valores, Aplanar) + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + Public Shared Sub RellenaFormularioPDF(PdfOrigen As String, PdfDestino As String, Valores As String, Optional Aplanar As Boolean = True) + Try + Dim fs As New FileStream(PdfDestino, FileMode.CreateNew, FileAccess.Write) + Dim ht As New Hashtable + Dim pares = Valores.Split("|") + For Each par In pares + ht.Add(par.Split(":")(0), par.Split(":")(1)) + Next + RellenaFormularioPDF(PdfOrigen, fs, ht, Aplanar) + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + + Public Shared Sub RellenaFormularioPDF(PdfOrigen As String, Destino As Stream, Valores As Hashtable, Optional Aplanar As Boolean = True) + Try + Dim pdfReader As New PdfReader(PdfOrigen) + Dim pdfDoc As New PdfDocument(pdfReader, New PdfWriter(Destino)) + Dim form = PdfAcroForm.GetAcroForm(pdfDoc, True) + For Each Valor As DictionaryEntry In Valores + Try + form.GetField(Valor.Key).SetValue(Valor.Value) + Catch ex As Exception + End Try + Next + If Aplanar Then + form.FlattenFields() + End If + pdfDoc.Close() + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + + Public Shared Function RellenaFormularioPDF(PdfOrigen As Byte(), ListaValores As List(Of Hashtable), Optional Aplanar As Boolean = True) As Byte() + + Try + + Dim i, j As Integer + Dim iosPdf(ListaValores.Count - 1)() As Byte + For i = 0 To ListaValores.Count - 1 + Dim pdfReader As New PdfReader(New MemoryStream(PdfOrigen)) + Dim Valores = ListaValores(i) + Dim ms As New MemoryStream + Dim pdfDoc As New PdfDocument(pdfReader, New PdfWriter(ms)) + Dim form = PdfAcroForm.GetAcroForm(pdfDoc, True) + For Each Valor As DictionaryEntry In Valores + Try + form.GetField(Valor.Key).SetValue(Valor.Value) + Catch ex As Exception + End Try + Next + If Aplanar Then + form.FlattenFields() + End If + pdfDoc.Close() + ' ms.Seek(0, 0) + iosPdf(i) = ms.ToArray + Next + If ListaValores.Count = 1 Then + Return iosPdf(0).ToArray + Else + Return UnePdfs(iosPdf) + End If + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Function + + Public Shared Function RellenaFormularioPDF(PdfOrigen As Byte(), Valores As Hashtable, Optional Aplanar As Boolean = True) As Byte() + + Try + Dim pdfReader As New PdfReader(New MemoryStream(PdfOrigen)) + Dim ms As New MemoryStream + Dim pdfDoc As New PdfDocument(pdfReader, New PdfWriter(ms)) + Dim form = PdfAcroForm.GetAcroForm(pdfDoc, True) + For Each Valor As DictionaryEntry In Valores + Try + Dim f = form.GetField(Valor.Key) + If f IsNot Nothing Then f.SetValue(Valor.Value) + Catch ex As Exception + End Try + Next + If Aplanar Then + form.FlattenFields() + End If + pdfDoc.Close() + If ms.CanSeek Then ms.Seek(0, 0) + Return ms.ToArray + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Function + Public Shared Sub AñadePropiedadesAPdf(ByVal FicheroPdf As String, ByVal Valores As Hashtable) + + Try + Dim ms As New MemoryStream + Dim pdfReader As New PdfReader(FicheroPdf) + Dim pdfDoc As New PdfDocument(pdfReader, New PdfWriter(ms)) + Dim info = pdfDoc.GetDocumentInfo() + + Dim newInfo As IDictionary(Of String, String) = New Dictionary(Of String, String)() + For Each Valor As DictionaryEntry In Valores + Try + newInfo.Add(CStr(Valor.Key), Valor.Value) + Catch ex As Exception + End Try + Next + info.SetMoreInfo(newInfo) + pdfDoc.Close() + Dim b = ms.ToArray + pdfReader.Close() + System.IO.File.Delete(FicheroPdf) + System.IO.File.WriteAllBytes(FicheroPdf, b) + + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + Public Shared Sub AñadePropiedadesAPdf(pdfDoc As PdfDocument, ByVal Valores As Hashtable) + + Try + Dim ms As New MemoryStream + + Dim info = pdfDoc.GetDocumentInfo() + + Dim newInfo As IDictionary(Of String, String) = New Dictionary(Of String, String)() + For Each Valor As DictionaryEntry In Valores + Try + newInfo.Add(Valor.Key, Valor.Value) + Catch ex As Exception + End Try + Next + info.SetMoreInfo(newInfo) + pdfDoc.Close() + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + Public Shared Function AñadePropiedadesAPdf(ByVal FicheroPdf As MemoryStream, ByVal Valores As Hashtable) As MemoryStream + Try + Dim ms As New MemoryStream + Dim pdfReader As New PdfReader(FicheroPdf) + Dim pdfDoc As New PdfDocument(pdfReader, New PdfWriter(ms)) + Dim info = pdfDoc.GetDocumentInfo() + + Dim newInfo As IDictionary(Of String, String) = New Dictionary(Of String, String)() + For Each Valor As DictionaryEntry In Valores + Try + newInfo.Add(Valor.Key, Valor.Value) + Catch ex As Exception + End Try + Next + info.SetMoreInfo(newInfo) + pdfDoc.Close() + If ms.CanSeek Then ms.Seek(0, 0) + Return ms + Catch ex As Exception + Throw New Exception(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) + Dim contenido As String = "" + Dim Estrategia = New SimpleTextExtractionStrategy + For page As Integer = 1 To pdfDoc.GetNumberOfPages() + Dim pagina = pdfDoc.GetPage(page) + Dim Texto = PdfTextExtractor.GetTextFromPage(pagina, Estrategia) + contenido &= Texto & vbCrLf + Next + pdfDoc.Close() + pdfReader.Close() + Return contenido + End Function + + Public Shared Sub PruebaSellado(FicheroASellar As String, FicheroDestino As String) + Dim pdfr As New PdfReader(FicheroASellar) + Dim pdfw = New PdfWriter(FicheroDestino) + Dim wp As New WriterProperties + 'Dim cl = pdfr.GetPdfAConformanceLevel() + + + Dim pdfd As New PdfDocument(pdfr, pdfw) + + + Dim np = pdfd.GetNumberOfPages + Dim fuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN) + + For i = 1 To np + Dim page As PdfPage = pdfd.GetPage(i) + Dim pdfc As New PdfCanvas(page) + Dim fc As New PdfCanvas(page.NewContentStreamAfter, page.GetResources, pdfd) + + pdfc.BeginText() + pdfc.SetFontAndSize(fuente, 12) + + pdfc.MoveText(0, 0) + + pdfc.ShowText("PRUEBA PRUEBA") + + pdfc.EndText() + + Next + + pdfd.Close() + pdfw.Close() + End Sub +End Class + diff --git a/TSPdfUtils/tsPDFUtilException.vb b/TSPdfUtils/tsPDFUtilException.vb new file mode 100644 index 0000000..02e960b --- /dev/null +++ b/TSPdfUtils/tsPDFUtilException.vb @@ -0,0 +1,8 @@ +Public Class tsPDFUtilException + Inherits Exception + Public ReadOnly Property ErrorCode As String + Public Sub New(ByVal paramName As String, ByVal CodigoError As String) + MyBase.New(paramName) + ErrorCode = ErrorCode + End Sub +End Class diff --git a/tsPDFUtilsCore/DatosFuente.cs b/tsPDFUtilsCore/DatosFuente.cs new file mode 100644 index 0000000..52f4360 --- /dev/null +++ b/tsPDFUtilsCore/DatosFuente.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace tsPDFUtilsCore +{ + public class DatosFuente + { + public string NombreFuente; + public byte[] memoryFont; + } + +} diff --git a/tsPDFUtilsCore/DatosImagenFirma.cs b/tsPDFUtilsCore/DatosImagenFirma.cs new file mode 100644 index 0000000..3304e76 --- /dev/null +++ b/tsPDFUtilsCore/DatosImagenFirma.cs @@ -0,0 +1,18 @@ + +using static tsPDFUtilsCore.Enums; + +namespace tsPDFUtilsCore +{ + + public class DatosImagenFirma + { + public EsquinaEnum PosicionEsquina; + public int Ancho; + public int Alto; + public int PosicionX; + public int PosicionY; + public byte[] Imagen; + public int AnguloRotacion; + } + +} diff --git a/tsPDFUtilsCore/DatosTextos.cs b/tsPDFUtilsCore/DatosTextos.cs new file mode 100644 index 0000000..6964a1b --- /dev/null +++ b/tsPDFUtilsCore/DatosTextos.cs @@ -0,0 +1,23 @@ +using System.Drawing; +using static tsPDFUtilsCore.Enums; + + + +namespace tsPDFUtilsCore +{ + + public class DatosTextos + { + public string NombreFuente; + public EsquinaEnum PosicionEsquina; + public AlineamientoEnum AlineacionTexto; + public float size; + public float anguloRotacion; + public string texto; + public int distanciaX; + public int distanciaY; + public Brush colorTexto; + public FontStyle estiloTexto; + } + +} diff --git a/tsPDFUtilsCore/Enums.cs b/tsPDFUtilsCore/Enums.cs new file mode 100644 index 0000000..829e3a9 --- /dev/null +++ b/tsPDFUtilsCore/Enums.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace tsPDFUtilsCore +{ + public class Enums + + { + + public enum EsquinaEnum : int + { + INFERIOR_IZQUIERDA = 0, + INFERIOR_DERECHA = 1, + SUPERIOR_IZQUIERDA = 2, + SUPERIOR_DERECHA = 3 + } + + public enum AlineamientoEnum : int + { + IZQUIERDA = 0, + DERECHA = 1, + CENTRO = 2, + JUSTIFICADO = 3 + } + public enum FuenteEnum + { + COURIER, + COURIER_BOLD, + COURIER_BOLDOBLIQUE, + COURIER_OBLIQUE, + HELVETICA, + HELVETICA_BOLD, + HELVETICA_BOLDOBLIQUE, + HELVETICA_OBLIQUE, + SYMBOL, + TIMES_ROMAN, + TIMES_BOLD, + TIMES_BOLDITALIC, + TIMES_ITALIC, + ZAPFDINGBATS + } + + } +} diff --git a/tsPDFUtilsCore/ImagenEnPdf.cs b/tsPDFUtilsCore/ImagenEnPdf.cs new file mode 100644 index 0000000..b54d65e --- /dev/null +++ b/tsPDFUtilsCore/ImagenEnPdf.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace tsPDFUtilsCore +{ + public class ImagenEnPdf + { + public byte[] Imagen { get; set; } + public EsquinaEnum EsquinaReferencia { get; set; } + public double Transparencia { get; set; } = 1; + public float CoordenadaX { get; set; } + public float CoordenadaY { get; set; } + public float EscalaVertical { get; set; } + public float EscalaHorizontal { get; set; } + public float AnguloRotacion { get; set; } + public int PaginaInicio { get; set; } + public int PaginaFin { get; set; } + } +} diff --git a/tsPDFUtilsCore/Imagenes.cs b/tsPDFUtilsCore/Imagenes.cs new file mode 100644 index 0000000..beabeec --- /dev/null +++ b/tsPDFUtilsCore/Imagenes.cs @@ -0,0 +1,580 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using System.Drawing; +using System.Drawing.Imaging; +using System.Drawing.Text; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using TSpdf.Kernel.Font; +using TSpdf.IO.Font.Constants; +using static tsPDFUtilsCore.Imagenes; +using static tsPDFUtilsCore.Enums; + + +namespace tsPDFUtilsCore +{ + public class Imagenes + { + public static Bitmap RedimensionaImagen(Bitmap Original, int NuevoAlto, int NuevoAncho, int CoordenadaX, int CoordenadaY) + { + Bitmap newImage = new Bitmap(NuevoAncho, NuevoAlto); + using (Graphics g = Graphics.FromImage(newImage)) + { + g.Clear(Color.Transparent); + g.DrawImage(Original, CoordenadaX, CoordenadaY, Original.Width, Original.Height); + } + return newImage; + } + + public static Bitmap crearBitMapFirmas(List listadoTextos, List listadoImagenes, List ListadoFuentes, TSpdf.Kernel.Geom.Rectangle medidas) + { + int dpi = 600; + + int anchoPixeles = (int)medidas.GetWidth() * dpi / 72; + int altoPixeles = (int)medidas.GetHeight() * dpi / 72; + + Bitmap bmp = new Bitmap(anchoPixeles, altoPixeles, PixelFormat.Format32bppArgb); + bmp.SetResolution(dpi, dpi); + Graphics g = Graphics.FromImage(bmp); + + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + + g.Clear(Color.Transparent); + + + + + // Dibujar textos + foreach (var texto in listadoTextos) + { + Font font = null; + + if (FontFamily.Families.Any(x => x.Name == texto.NombreFuente)) + font = new Font(FontFamily.Families.Where(x => x.Name == texto.NombreFuente).First(), texto.size, texto.estiloTexto, GraphicsUnit.Point); + else if (ListadoFuentes != null && ListadoFuentes.Any(x => x.NombreFuente == texto.NombreFuente)) + { + var fuente = ListadoFuentes.Where(x => x.NombreFuente == texto.NombreFuente).First(); + + int dataLength = fuente.memoryFont.Length; + IntPtr ptrData = Marshal.AllocCoTaskMem(dataLength); + + Marshal.Copy(fuente.memoryFont, 0, ptrData, dataLength); + + PrivateFontCollection privateFontCollection = new PrivateFontCollection(); + privateFontCollection.AddMemoryFont(ptrData, dataLength); + + font = new Font(privateFontCollection.Families.FirstOrDefault(), texto.size, texto.estiloTexto, GraphicsUnit.Point); + } + else + throw new Exception("Fuente " + texto.NombreFuente + " no encontrada"); + + + var sizeTexto = g.MeasureString(texto.texto, font); + + var coordenadaX = calcularX(texto.distanciaX, texto.distanciaY, texto.PosicionEsquina, bmp, texto.anguloRotacion, (int)sizeTexto.Width, (int)sizeTexto.Height); + var coordenadaY = calcularY(texto.distanciaY, texto.distanciaX, texto.PosicionEsquina, bmp, texto.anguloRotacion, (int)sizeTexto.Width, (int)sizeTexto.Height); + + PointF rotationPoint = new PointF(texto.distanciaX, texto.distanciaY); + + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit; + + StringFormat stringFormat = new StringFormat(); + + g.TranslateTransform(0, 0); + g.RotateTransform(texto.anguloRotacion); + + if (texto.AlineacionTexto != AlineamientoEnum.JUSTIFICADO) + g.DrawString(texto.texto, font, texto.colorTexto, new PointF(coordenadaX, coordenadaY)); + else + DrawJustifiedString(ref g, texto.texto, font, texto.colorTexto, new RectangleF(coordenadaX, coordenadaY, altoPixeles, sizeTexto.Height), stringFormat); + g.ResetTransform(); + } + + foreach (var imagen in listadoImagenes) + { + MemoryStream stream = new MemoryStream(); + stream.Write(imagen.Imagen, 0, imagen.Imagen.Length); + System.Drawing.Image myImage = System.Drawing.Image.FromStream(stream); + + g.TranslateTransform(0, 0); + g.RotateTransform(imagen.AnguloRotacion); + + int coordenadaX = calcularX(imagen.PosicionX, imagen.PosicionY, imagen.PosicionEsquina, bmp, imagen.AnguloRotacion, myImage.Width, myImage.Height); + int coordenadaY = calcularY(imagen.PosicionY, imagen.PosicionX, imagen.PosicionEsquina, bmp, imagen.AnguloRotacion, myImage.Width, myImage.Height); + + if (altoPixeles > anchoPixeles) + g.DrawImage(myImage, new Rectangle(coordenadaX, coordenadaY, altoPixeles, anchoPixeles)); + else + g.DrawImage(myImage, new Rectangle(coordenadaX, coordenadaY, anchoPixeles, altoPixeles)); + + + g.ResetTransform(); + } + return bmp; + } + public static byte[] BitMapAByteArray(Bitmap bmp) + { + MemoryStream msSalida = new MemoryStream(); + bmp.Save(msSalida, ImageFormat.Png); + byte[] byteArray = msSalida.ToArray(); + msSalida.Close(); + return byteArray; + } + + public static void DrawJustifiedString(ref Graphics g, string txt, Font font, Brush brush, RectangleF destination, StringFormat format) + { + using (StringFormat fmt = (StringFormat)format.Clone()) + { + fmt.FormatFlags = fmt.FormatFlags | StringFormatFlags.MeasureTrailingSpaces; + CharacterRange[] cr = { new CharacterRange(0, 1) }; + fmt.SetMeasurableCharacterRanges(cr); + fmt.Alignment = StringAlignment.Center; + + float txtWidth = g.MeasureString(txt, font, destination.Location, fmt).Width; + float spacing = (destination.Width - txtWidth) / (txt.Length - 1); + + RectangleF dest = new RectangleF(destination.X, destination.Y, 0, destination.Height); + foreach (char c in txt.AsEnumerable()) + { + // Graphics#MeasureString is too inaccurate for character placement + dest.Width = g.MeasureCharacterRanges(c.ToString(), font, destination, fmt)[0].GetBounds(g).Width; + g.DrawString(c.ToString(), font, brush, dest, fmt); + dest.X += dest.Width + spacing; + } + } + } + + private static int calcularX(int coordenadaX, int coordenadaY, EsquinaEnum PosicionEsquina, Bitmap medidasFirma, float rotacionElemento, int anchoElemento, int altoElemento) + { + int anchoFirma = medidasFirma.Width; + int altoFirma = medidasFirma.Height; + + int ejeX = 0; + + switch (PosicionEsquina) + { + case EsquinaEnum.SUPERIOR_IZQUIERDA: + { + switch (rotacionElemento) + { + case 90: + { + ejeX = coordenadaY; + break; + } + + case 180: + { + ejeX = -coordenadaX - anchoElemento; + break; + } + + case 270: + { + ejeX = -coordenadaY - anchoElemento; + break; + } + + default: + { + ejeX = coordenadaX; + break; + } + } + + break; + } + + case EsquinaEnum.INFERIOR_IZQUIERDA: + { + switch (rotacionElemento) + { + case 90: + { + ejeX = (altoFirma - (coordenadaY + anchoElemento)); + break; + } + + case 180: + { + ejeX = -coordenadaX - anchoElemento; + break; + } + + case 270: + { + ejeX = coordenadaY - altoFirma; + break; + } + + default: + { + ejeX = coordenadaX; + break; + } + } + + break; + } + + case EsquinaEnum.SUPERIOR_DERECHA: + { + switch (rotacionElemento) + { + case 90: + { + ejeX = coordenadaY; + break; + } + + case 180: + { + ejeX = -anchoFirma + coordenadaX; + break; + } + + case 270: + { + ejeX = -coordenadaY - anchoElemento; + break; + } + + default: + { + ejeX = anchoFirma - (anchoElemento + coordenadaX); + break; + } + } + + break; + } + + case EsquinaEnum.INFERIOR_DERECHA: + { + switch (rotacionElemento) + { + case 90: + { + ejeX = (altoFirma - (coordenadaY + anchoElemento)); + break; + } + + case 180: + { + ejeX = -anchoFirma + coordenadaX; + break; + } + + case 270: + { + ejeX = coordenadaY - altoFirma; + break; + } + + default: + { + ejeX = anchoFirma - (anchoElemento + coordenadaX); + break; + } + } + + break; + } + } + + return ejeX; + } + private static int calcularY(int coordenadaY, int coordenadaX, EsquinaEnum PosicionEsquina, Bitmap medidasFirma, float rotacionElemento, int anchoElemento, int altoElemento) + { + int anchoFirma = medidasFirma.Width; + int altoFirma = medidasFirma.Height; + int ejeY = 0; + + switch (PosicionEsquina) + { + case EsquinaEnum.SUPERIOR_IZQUIERDA: + { + switch (rotacionElemento) + { + case 90: + { + ejeY = -coordenadaX - altoElemento; + break; + } + + case 180: + { + ejeY = -coordenadaY - altoElemento; + break; + } + + case 270: + { + ejeY = coordenadaX; + break; + } + + default: + { + ejeY = coordenadaY; + break; + } + } + + break; + } + + case EsquinaEnum.INFERIOR_IZQUIERDA: + { + switch (rotacionElemento) + { + case 90: + { + ejeY = -coordenadaX - altoElemento; + break; + } + + case 180: + { + ejeY = -altoFirma + coordenadaY; + break; + } + + case 270: + { + ejeY = coordenadaX; + break; + } + + default: + { + ejeY = altoFirma - (altoElemento + coordenadaY); + break; + } + } + + break; + } + + case EsquinaEnum.SUPERIOR_DERECHA: + { + switch (rotacionElemento) + { + case 90: + { + ejeY = coordenadaX - (anchoFirma); + break; + } + + case 180: + { + ejeY = -coordenadaY - altoElemento; + break; + } + + case 270: + { + ejeY = anchoFirma - (altoElemento + coordenadaX); + break; + } + + default: + { + ejeY = coordenadaY; + break; + } + } + + break; + } + + case EsquinaEnum.INFERIOR_DERECHA: + { + switch (rotacionElemento) + { + case 90: + { + ejeY = coordenadaX - (anchoFirma); + break; + } + + case 180: + { + ejeY = -altoFirma + coordenadaY; + break; + } + + case 270: + { + ejeY = anchoFirma - (altoElemento + coordenadaX); + break; + } + + default: + { + ejeY = altoFirma - (altoElemento + coordenadaY); + break; + } + } + + break; + } + } + + return ejeY; + } + private static TSpdf.Kernel.Font.PdfFont devolverTipoFuente(string nombreFuente) + { + TSpdf.Kernel.Font.PdfFont tipoFuente; + + switch (nombreFuente) + { + case "COURIER": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.COURIER); + break; + } + + case "COURIER_BOLD": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.COURIER_BOLD); + break; + } + + case "COURIER_BOLDOBLIQUE": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.COURIER_BOLDOBLIQUE); + break; + } + + case "COURIER_OBLIQUE": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.COURIER_OBLIQUE); + break; + } + + case "HELVETICA": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.HELVETICA); + break; + } + + case "HELVETICA_BOLD": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLD); + break; + } + + case "HELVETICA_BOLDOBLIQUE": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLDOBLIQUE); + break; + } + + case "HELVETICA_OBLIQUE": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_OBLIQUE); + break; + } + + case "SYMBOL": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.SYMBOL); + break; + } + + case "TIMES_ROMAN": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN); + break; + } + + case "TIMES_BOLD": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_BOLD); + break; + } + + case "TIMES_BOLDITALIC": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_BOLDITALIC); + break; + } + + case "TIMES_ITALIC": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_ITALIC); + break; + } + + case "ZAPFDINGBATS": + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.ZAPFDINGBATS); + break; + } + + default: + { + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN); + break; + } + } + + return tipoFuente; + } + + } + +} + + + public enum EsquinaEnum : int + { + INFERIOR_IZQUIERDA = 0, + INFERIOR_DERECHA = 1, + SUPERIOR_IZQUIERDA = 2, + SUPERIOR_DERECHA = 3 + } + + public enum AlineamientoEnum : int + { + IZQUIERDA = 0, + DERECHA = 1, + CENTRO = 2, + JUSTIFICADO = 3, + JUSTIFICADO_ALL = 4 + } + + + public enum TipoTextoEnum : int + { + NORMAL = 0, + NUMERO_PAGINA = 1, + NUMERO_PAGINA_TOTAL_PAGINAS = 2, + } + public enum FuenteEnum + { + COURIER, + COURIER_BOLD, + COURIER_BOLDOBLIQUE, + COURIER_OBLIQUE, + HELVETICA, + HELVETICA_BOLD, + HELVETICA_BOLDOBLIQUE, + HELVETICA_OBLIQUE, + SYMBOL, + TIMES_ROMAN, + TIMES_BOLD, + TIMES_BOLDITALIC, + TIMES_ITALIC, + ZAPFDINGBATS + } + + + + + diff --git a/tsPDFUtilsCore/Properties/Resources.Designer.cs b/tsPDFUtilsCore/Properties/Resources.Designer.cs new file mode 100644 index 0000000..dcfa46b --- /dev/null +++ b/tsPDFUtilsCore/Properties/Resources.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// Este código fue generado por una herramienta. +// Versión de runtime:4.0.30319.42000 +// +// Los cambios en este archivo podrían causar un comportamiento incorrecto y se perderán si +// se vuelve a generar el código. +// +//------------------------------------------------------------------------------ + +namespace tsPDFUtilsCore.Properties { + using System; + + + /// + /// Clase de recurso fuertemente tipado, para buscar cadenas traducidas, etc. + /// + // StronglyTypedResourceBuilder generó automáticamente esta clase + // a través de una herramienta como ResGen o Visual Studio. + // Para agregar o quitar un miembro, edite el archivo .ResX y, a continuación, vuelva a ejecutar ResGen + // con la opción /str o recompile su proyecto de VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Devuelve la instancia de ResourceManager almacenada en caché utilizada por esta clase. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("tsPDFUtilsCore.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Reemplaza la propiedad CurrentUICulture del subproceso actual para todas las + /// búsquedas de recursos mediante esta clase de recurso fuertemente tipado. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Busca un recurso adaptado de tipo System.Byte[]. + /// + public static byte[] fogra39L { + get { + object obj = ResourceManager.GetObject("fogra39L", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Busca un recurso adaptado de tipo System.Byte[]. + /// + public static byte[] sRGB2014 { + get { + object obj = ResourceManager.GetObject("sRGB2014", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/tsPDFUtilsCore/Properties/Resources.resx b/tsPDFUtilsCore/Properties/Resources.resx new file mode 100644 index 0000000..e16100c --- /dev/null +++ b/tsPDFUtilsCore/Properties/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\fogra39L.icc;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\sRGB2014.icc;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/tsPDFUtilsCore/Resources/fogra39L.icc b/tsPDFUtilsCore/Resources/fogra39L.icc new file mode 100644 index 0000000..8cba868 Binary files /dev/null and b/tsPDFUtilsCore/Resources/fogra39L.icc differ diff --git a/tsPDFUtilsCore/Resources/sRGB2014.icc b/tsPDFUtilsCore/Resources/sRGB2014.icc new file mode 100644 index 0000000..49afbfe Binary files /dev/null and b/tsPDFUtilsCore/Resources/sRGB2014.icc differ diff --git a/tsPDFUtilsCore/Sellado.cs b/tsPDFUtilsCore/Sellado.cs new file mode 100644 index 0000000..3971ff2 --- /dev/null +++ b/tsPDFUtilsCore/Sellado.cs @@ -0,0 +1,1446 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using TSpdf.IO.Font.Constants; +using TSpdf.IO.Image; +using TSpdf.Kernel.Colors; +using TSpdf.Kernel.Font; +using TSpdf.Kernel.Geom; +using TSpdf.Kernel.Pdf.Canvas; +using TSpdf.Kernel.Pdf.Extgstate; +using TSpdf.Layout; +using TSpdf.Layout.Element; +using TSpdf.Layout.Properties; +using TSpdf.Layout.Splitting; +using static tsPDFUtilsCore.Enums; + +namespace tsPDFUtilsCore +{ + public class Sellado + { + + public async static Task SellaPDFAsync(Stream PdfOrigen, Stream PdfDestino, List TextosAInsertar, List ImagenesAInsertar, DelegadoProgreso dlgpr = null, CancellationTokenSource cts = null, bool ConvertirPDFA = false, bool PDFAObligatorio=false) + { + bool Res = false; + await Task.Factory.StartNew(() => + { + Res = SellaPDF(PdfOrigen, PdfDestino, TextosAInsertar, ImagenesAInsertar, dlgpr, cts, ConvertirPDFA, PDFAObligatorio); + }); + return Res; + } + public static bool SellaPDF(Stream PdfOrigen, Stream PdfDestino, List TextosAInsertar, List ImagenesAInsertar, DelegadoProgreso dlgpr = null, CancellationTokenSource cts = null, bool ConvertirPDFA = false, bool PDFAObligatorio=false) + { + try + { + Progreso prg = new Progreso(); + if (dlgpr != null) + { + prg.Estado = "Cargando Documento"; + dlgpr.Invoke(prg); + } + + + Stream pdfLectura; + + if (ConvertirPDFA) + { + try + { + pdfLectura = Utilidades.crearPDFA(PdfOrigen); + } + catch (Exception ex) + { + if (PDFAObligatorio) + { + throw new Exception(ex.Message, ex); + } + else + { + PdfOrigen.Position = 0; + pdfLectura = PdfOrigen; + } + } + } + else + { + pdfLectura = PdfOrigen; + } + + + TSpdf.Kernel.Pdf.PdfReader reader = new TSpdf.Kernel.Pdf.PdfReader(pdfLectura); + reader.SetUnethicalReading(true); + + TSpdf.Kernel.Pdf.PdfWriter writer = new TSpdf.Kernel.Pdf.PdfWriter(PdfDestino); + + TSpdf.Kernel.Pdf.PdfDocument pdf = new TSpdf.Kernel.Pdf.PdfDocument(reader, writer); + + Document document = new Document(pdf); + + int numeroPaginas = pdf.GetNumberOfPages(); + + if (dlgpr != null) + { + + prg.NumeroPaginasTotal = numeroPaginas; + prg.Estado = "Sellando Documento"; + dlgpr.Invoke(prg); + } + + Image[] imagenes = null; + if (ImagenesAInsertar != null) + { + imagenes = new Image[ImagenesAInsertar.Count]; + for (int k = 0; k < ImagenesAInsertar.Count; k++) + { + if (cts != null && cts.Token.IsCancellationRequested) return false; + ImageData imageData = ImageDataFactory.Create(ImagenesAInsertar[k].Imagen); + imagenes[k] = new Image(imageData); + } + } + + + for (int i = 1; i <= numeroPaginas; i++) + { + if (cts != null && cts.Token.IsCancellationRequested) return false; + if (dlgpr != null) + { + prg.NumeroPaginaActual = i; + prg.Estado = "Procesando Página " + i.ToString() + " / " + prg.NumeroPaginasTotal.ToString(); + dlgpr.Invoke(prg); + } + if (TextosAInsertar != null) + { + for (int j = 0; j < TextosAInsertar.Count; j++) + { + if (cts != null && cts.Token.IsCancellationRequested) return false; + if (TextosAInsertar[j].PaginaInicio <= i && (TextosAInsertar[j].PaginaFin >= i || TextosAInsertar[j].PaginaFin == 0)) + { + controlarTextos(pdf, TextosAInsertar[j],i,numeroPaginas); + } + } + } + if (ImagenesAInsertar != null) + { + if (cts != null && cts.Token.IsCancellationRequested) return false; + for (int k = 0; k < ImagenesAInsertar.Count; k++) + if (ImagenesAInsertar[k].PaginaInicio <= i && (ImagenesAInsertar[k].PaginaFin >= i || ImagenesAInsertar[k].PaginaFin == 0)) + { + controlarImagenes(pdf, ImagenesAInsertar[k], i, imagenes[k]); + } + } + } + + pdf.Close(); + document.Close(); + writer.Close(); + writer.Dispose(); + reader.Close(); + if (cts != null && cts.IsCancellationRequested) + return false; + else return true; + } + catch (Exception ex) + { + throw new Exception(ex.Message,ex); + } + } + public static bool SellaPDF(string PdfOrigen, string PdfDestino, List TextosAInsertar, List ImagenesAInsertar, DelegadoProgreso dlgpr = null, CancellationTokenSource cts = null, bool ConvertirPDFA = false, bool PDFAObligatorio=false) + { + try + { + Progreso prg = new Progreso(); + if (dlgpr != null) + { + prg.Estado = "Cargando Documento"; + dlgpr.Invoke(prg); + } + + Stream pdfLectura; + if (ConvertirPDFA) + { + FileStream fs = null; + try + { + fs = new FileStream(PdfOrigen, FileMode.Open, FileAccess.Read); + pdfLectura = Utilidades.crearPDFA(fs); + } + catch (Exception ex) + { + if (fs != null) fs.Close(); + if(PDFAObligatorio) + { + throw new Exception(ex.Message,ex); + } + else + { + pdfLectura = new FileStream(PdfOrigen, FileMode.Open, FileAccess.Read); + } + } + } + else + { + pdfLectura = new FileStream(PdfOrigen, FileMode.Open, FileAccess.Read); + } + + TSpdf.Kernel.Pdf.PdfReader reader = new TSpdf.Kernel.Pdf.PdfReader(pdfLectura); + reader.SetUnethicalReading(true); + + TSpdf.Kernel.Pdf.PdfWriter writer = new TSpdf.Kernel.Pdf.PdfWriter(PdfDestino); + + TSpdf.Kernel.Pdf.PdfDocument pdf = new TSpdf.Kernel.Pdf.PdfDocument(reader, writer); + + Document document = new Document(pdf); + + int numeroPaginas = pdf.GetNumberOfPages(); + + if (dlgpr != null) + { + + prg.NumeroPaginasTotal = numeroPaginas; + prg.Estado = "Sellando Documento"; + dlgpr.Invoke(prg); + } + + Image[] imagenes = null; + if (ImagenesAInsertar != null) + { + imagenes = new Image[ImagenesAInsertar.Count]; + for (int k = 0; k < ImagenesAInsertar.Count; k++) + { + if (cts != null && cts.Token.IsCancellationRequested) return false; + ImageData imageData = ImageDataFactory.Create(ImagenesAInsertar[k].Imagen); + imagenes[k] = new Image(imageData); + } + } + + + for (int i = 1; i <= numeroPaginas; i++) + { + if (cts != null && cts.Token.IsCancellationRequested) return false; + if (dlgpr != null) + { + prg.NumeroPaginaActual = i; + prg.Estado = "Procesando Página " + i.ToString() + " / " + prg.NumeroPaginasTotal.ToString(); + dlgpr.Invoke(prg); + } + if (TextosAInsertar != null) + { + for (int j = 0; j < TextosAInsertar.Count; j++) + { + if (cts != null && cts.Token.IsCancellationRequested) return false; + if (TextosAInsertar[j].PaginaInicio <= i && (TextosAInsertar[j].PaginaFin >= i || TextosAInsertar[j].PaginaFin == 0)) + { + controlarTextos(pdf, TextosAInsertar[j], i, numeroPaginas); + } + } + } + if (ImagenesAInsertar != null) + { + if (cts != null && cts.Token.IsCancellationRequested) return false; + for (int k = 0; k < ImagenesAInsertar.Count; k++) + if (ImagenesAInsertar[k].PaginaInicio <= i && (ImagenesAInsertar[k].PaginaFin >= i || ImagenesAInsertar[k].PaginaFin == 0)) + { + controlarImagenes(pdf, ImagenesAInsertar[k], i, imagenes[k]); + } + } + } + + pdf.Close(); + document.Close(); + writer.Close(); + writer.Dispose(); + reader.Close(); + if (cts != null && cts.IsCancellationRequested) + return false; + else return true; + } + catch (Exception ex) + { + throw new Exception(ex.Message, ex); + } + + } + + private static void controlarImagenes(TSpdf.Kernel.Pdf.PdfDocument pdf, ImagenEnPdf ImagenAInsertar, int numeroPagina, Image imagen) + { + TSpdf.Kernel.Pdf.PdfPage pagina = pdf.GetPage(numeroPagina); + Rectangle tamanoPagina = pagina.GetPageSizeWithRotation(); + + var anchoPagina = tamanoPagina.GetWidth(); + var altoPagina = tamanoPagina.GetHeight(); + + // Cambia el ancho y alto de la página dependiendo de sus medidas + tamanoPagina.SetWidth(comprobarAncho(anchoPagina, altoPagina, pagina.GetRotation())); + tamanoPagina.SetHeight(comprobarAlto(anchoPagina, altoPagina, pagina.GetRotation())); + + var cropBoxPagina = pagina.GetCropBox(); + var mediaBox = pagina.GetMediaBox(); + + Rectangle diferenciaTamano; + if (mediaBox.GetWidth() != cropBoxPagina.GetWidth() || mediaBox.GetHeight() != cropBoxPagina.GetHeight()) + { + diferenciaTamano = new Rectangle(cropBoxPagina.GetX() - mediaBox.GetX(), cropBoxPagina.GetY() - mediaBox.GetY(), mediaBox.GetWidth() - cropBoxPagina.GetWidth(), mediaBox.GetHeight() - cropBoxPagina.GetHeight()); + } + else + { + diferenciaTamano = new Rectangle(cropBoxPagina.GetX(), cropBoxPagina.GetY(), mediaBox.GetWidth(), mediaBox.GetHeight()); + } + + PdfCanvas pdfCanvas = new PdfCanvas(pagina); + PdfExtGState pdfExtGState = new PdfExtGState(); + pdfExtGState.SetFillOpacity((float)(1 - ImagenAInsertar.Transparencia)); + pdfExtGState.SetStrokeOpacity((float)(1 - ImagenAInsertar.Transparencia)); + pdfCanvas.SetExtGState(pdfExtGState); + + Canvas canvas = new Canvas(pdfCanvas, pagina.GetPageSizeWithRotation(), true); + + Image image = imagen.ScaleToFit(ImagenAInsertar.EscalaHorizontal, ImagenAInsertar.EscalaVertical).SetRotationAngle(ImagenAInsertar.AnguloRotacion); + + // Suma del ángulo del texto + el ángulo de la página, para que el ángulo final sea el correcto. + float anguloImgPag = ((ImagenAInsertar.AnguloRotacion + pagina.GetRotation())); + + medidasTextoImagen medidasImagenInsertar = comprobarMedidasImagen(pagina.GetRotation(), ImagenAInsertar, anguloImgPag,image); + + var distanciaX = calcularDistanciaX((int)ImagenAInsertar.EsquinaReferencia, tamanoPagina, ImagenAInsertar.CoordenadaX, medidasImagenInsertar.ancho, medidasImagenInsertar.alto, anguloImgPag, pagina.GetRotation(), diferenciaTamano); + var distanciaY = calcularDistanciaY((int)ImagenAInsertar.EsquinaReferencia, tamanoPagina, ImagenAInsertar.CoordenadaY, medidasImagenInsertar.ancho, medidasImagenInsertar.alto, anguloImgPag, pagina.GetRotation(), diferenciaTamano); + + + canvas.Add(image.SetFixedPosition(distanciaX + , distanciaY + ) + .SetRotationAngle((ImagenAInsertar.AnguloRotacion + pagina.GetRotation()) * (float)0.017453)); + + + canvas.Close(); + } + private static void controlarTextos(TSpdf.Kernel.Pdf.PdfDocument pdf, TextoEnPdf TextoAInsertar, int numeroPagina, int totalNumeroPagina) + { + TSpdf.Kernel.Pdf.PdfPage pagina = pdf.GetPage(numeroPagina); + + + Rectangle tamanoPagina = pagina.GetPageSizeWithRotation(); + + var anchoPagina = tamanoPagina.GetWidth(); + var altoPagina = tamanoPagina.GetHeight(); + + // Cambia el ancho y alto de la página dependiendo de sus medidas + tamanoPagina.SetWidth(comprobarAncho(anchoPagina, altoPagina, pagina.GetRotation())); + tamanoPagina.SetHeight(comprobarAlto(anchoPagina, altoPagina, pagina.GetRotation())); + + var cropBoxPagina = pagina.GetCropBox(); + var mediaBox = pagina.GetMediaBox(); + PdfCanvas pdfCanvas = new PdfCanvas(pagina); + + // Suma del ángulo del texto + el ángulo de la página, para que el ángulo final sea el correcto. + float anguloTextPag = (TextoAInsertar.AnguloRotacion + pagina.GetRotation()); + + + Rectangle diferenciaTamano; + if (mediaBox.GetWidth() != cropBoxPagina.GetWidth() || mediaBox.GetHeight() != cropBoxPagina.GetHeight()) + { + diferenciaTamano = new Rectangle(cropBoxPagina.GetX() - mediaBox.GetX(), cropBoxPagina.GetY() - mediaBox.GetY(), mediaBox.GetWidth() - cropBoxPagina.GetWidth(), mediaBox.GetHeight() - cropBoxPagina.GetHeight()); + } + else + { + diferenciaTamano = new Rectangle(cropBoxPagina.GetX(), cropBoxPagina.GetY(), mediaBox.GetWidth(), mediaBox.GetHeight()); + } + + PdfExtGState pdfExtGState = new PdfExtGState(); + pdfExtGState.SetFillOpacity((float)(1 - TextoAInsertar.Transparencia)); + pdfCanvas.SetExtGState(pdfExtGState); + Canvas canvas = new Canvas(pdfCanvas, pagina.GetPageSizeWithRotation(), true); + Paragraph texto = new Paragraph(TextoAInsertar.Texto) + .SetWidth(TextoAInsertar.anchoTexto) + .SetHeight(TextoAInsertar.altoTexto) + .SetRotationAngle(anguloTextPag * (float)0.017453) + //.SetBackgroundColor(new DeviceRgb(TextoAInsertar.Color.R, TextoAInsertar.Color.G, TextoAInsertar.Color.B)) + .SetTextAlignment(devolverAlineamiento((int)TextoAInsertar.Alineamiento)); + + canvas.SetFont(devolverTipoFuente(TextoAInsertar.Fuente.ToString())); + canvas.SetFontSize(TextoAInsertar.TamañoFuente); + canvas.SetFontColor(new DeviceRgb(TextoAInsertar.Color.R, TextoAInsertar.Color.G, TextoAInsertar.Color.B)); + + // Controla como se muestra el número de páginas + switch (TextoAInsertar.TipoTexto) + { + case TipoTextoEnum.NUMERO_PAGINA_TOTAL_PAGINAS: + TextoAInsertar.Texto = "PAG " + numeroPagina.ToString().PadLeft(4, '0') + "/" + totalNumeroPagina.ToString().PadLeft(4, '0'); + texto = new Paragraph(TextoAInsertar.Texto) + .SetWidth(TextoAInsertar.anchoTexto) + .SetHeight(TextoAInsertar.altoTexto) + /* Resultado de fórmula matemática(pi/180º) para que el + ángulo sea el correcto */ + .SetRotationAngle(anguloTextPag * (float)0.017453) + .SetTextAlignment(devolverAlineamiento((int)TextoAInsertar.Alineamiento)); + break; + } + + medidasTextoImagen medidasTextoInsertar = comprobarMedidasTexto(pagina.GetRotation(), TextoAInsertar, anguloTextPag); + + canvas.ShowTextAligned(texto, + calcularDistanciaX((int)TextoAInsertar.EsquinaReferencia, tamanoPagina, comprobarEjeXPagRotada(TextoAInsertar.CoordenadaX, TextoAInsertar.CoordenadaY, pagina.GetRotation()), medidasTextoInsertar.ancho, medidasTextoInsertar.alto, anguloTextPag, pagina.GetRotation(), diferenciaTamano), + calcularDistanciaY((int)TextoAInsertar.EsquinaReferencia, tamanoPagina, comprobarEjeYPagRotada(TextoAInsertar.CoordenadaX, TextoAInsertar.CoordenadaY, pagina.GetRotation()), medidasTextoInsertar.ancho, medidasTextoInsertar.alto, anguloTextPag, pagina.GetRotation(), diferenciaTamano), + devolverAlineamiento((int)TextoAInsertar.Alineamiento)); + + canvas.Close(); + } + public static medidasTextoImagen comprobarMedidasTexto(int rotacionPagina, TextoEnPdf TextoAInsertar, float anguloTextPag) + { + medidasTextoImagen medidas = new medidasTextoImagen(); + + switch (rotacionPagina) + { + case 0: + if (TextoAInsertar.AnguloRotacion != 0) + { + medidas.ancho = 0; + medidas.alto = 0; + } + else + { + if (TextoAInsertar.EsquinaReferencia == EsquinaEnum.INFERIOR_DERECHA || TextoAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_DERECHA) + { + medidas.alto = TextoAInsertar.altoTexto; + } + else + { + if (TextoAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_IZQUIERDA) + { + if (TextoAInsertar.AnguloRotacion == 0) + { + medidas.alto = TextoAInsertar.altoTexto; + } + else + { + medidas.ancho = TextoAInsertar.anchoTexto; + } + } + else + { + medidas.ancho = TextoAInsertar.anchoTexto; + medidas.alto = TextoAInsertar.altoTexto; + } + + } + + } + + break; + case 90: + if (TextoAInsertar.AnguloRotacion != 0) + { + if (TextoAInsertar.EsquinaReferencia == EsquinaEnum.INFERIOR_IZQUIERDA || TextoAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_IZQUIERDA) + { + medidas.ancho = TextoAInsertar.anchoTexto; + } + else + { + medidas.ancho = TextoAInsertar.anchoTexto; + medidas.alto = TextoAInsertar.altoTexto; + } + } + else + { + if (TextoAInsertar.EsquinaReferencia == EsquinaEnum.INFERIOR_IZQUIERDA || TextoAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_IZQUIERDA) + { + medidas.alto = TextoAInsertar.altoTexto; + medidas.ancho = TextoAInsertar.anchoTexto; + } + else + { + medidas.alto = TextoAInsertar.altoTexto; + } + } + break; + case 180: + if (TextoAInsertar.EsquinaReferencia == EsquinaEnum.INFERIOR_IZQUIERDA) + { + if (anguloTextPag == 270) + { + medidas.ancho = TextoAInsertar.altoTexto; + medidas.alto = TextoAInsertar.anchoTexto; + } + else + { + medidas.alto = TextoAInsertar.altoTexto; + } + } + else if (TextoAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_IZQUIERDA) + { + if (anguloTextPag == 180) + { + medidas.alto = TextoAInsertar.altoTexto; + } + else + { + medidas.ancho = TextoAInsertar.altoTexto; + } + } + else if (TextoAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_DERECHA) + { + if (anguloTextPag == 180) + { + medidas.alto = TextoAInsertar.altoTexto; + medidas.ancho = TextoAInsertar.anchoTexto; + } + else + { + medidas.ancho = TextoAInsertar.altoTexto; + } + } + else + { + medidas.ancho = TextoAInsertar.anchoTexto; + medidas.alto = TextoAInsertar.altoTexto; + } + + break; + case 270: + + if (TextoAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_DERECHA) + { + if (TextoAInsertar.AnguloRotacion == 90) + { + medidas.alto = TextoAInsertar.altoTexto; + } + else + { + medidas.ancho = TextoAInsertar.anchoTexto; + medidas.alto = TextoAInsertar.altoTexto; + } + } + if (TextoAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_IZQUIERDA) + { + if (TextoAInsertar.AnguloRotacion == 90) + { + medidas.alto = TextoAInsertar.altoTexto; + } + else + { + medidas.ancho = TextoAInsertar.anchoTexto; + + } + } + else if (TextoAInsertar.EsquinaReferencia != EsquinaEnum.SUPERIOR_DERECHA) + { + if (TextoAInsertar.AnguloRotacion == 0) + { + medidas.ancho = TextoAInsertar.anchoTexto; + medidas.alto = TextoAInsertar.altoTexto; + } + else + { + medidas.alto = TextoAInsertar.altoTexto; + } + } + break; + } + + + return medidas; + } + public static medidasTextoImagen comprobarMedidasImagen(int rotacionPagina, ImagenEnPdf ImagenAInsertar, float anguloImgPag, Image image) + { + medidasTextoImagen medidas = new medidasTextoImagen(); + + switch (rotacionPagina) + { + case 0: + if (ImagenAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_DERECHA || ImagenAInsertar.EsquinaReferencia == EsquinaEnum.INFERIOR_DERECHA) + { + medidas.alto = image.GetImageScaledHeight(); + medidas.ancho = image.GetImageScaledWidth(); + } + if (ImagenAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_IZQUIERDA) + { + if (ImagenAInsertar.AnguloRotacion == 0) + { + medidas.alto = image.GetImageScaledHeight(); + } + else + { + medidas.ancho = image.GetImageScaledWidth(); + medidas.alto = image.GetImageScaledHeight(); + } + } + else + { + medidas.alto = image.GetImageScaledHeight(); + } + break; + case 90: + switch (anguloImgPag) + { + case 180: + if (ImagenAInsertar.EsquinaReferencia == EsquinaEnum.INFERIOR_DERECHA || ImagenAInsertar.EsquinaReferencia == EsquinaEnum.INFERIOR_IZQUIERDA) + { + medidas.alto = image.GetImageScaledHeight(); + medidas.ancho = image.GetImageScaledWidth(); + } + else + { + medidas.alto = image.GetImageScaledHeight(); + } + break; + + case 90: + if (ImagenAInsertar.EsquinaReferencia != EsquinaEnum.SUPERIOR_IZQUIERDA) + { + if (ImagenAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_DERECHA) + { + medidas.ancho = image.GetImageScaledWidth(); + } + else + { + medidas.alto = image.GetImageScaledHeight(); + } + + if (ImagenAInsertar.EsquinaReferencia == EsquinaEnum.INFERIOR_DERECHA) + { + medidas.ancho = image.GetImageScaledWidth(); + medidas.alto = image.GetImageScaledHeight(); + } + } + break; + default: + medidas.ancho = image.GetImageScaledWidth(); + medidas.alto = image.GetImageScaledHeight(); + break; + } + break; + case 180: + if (ImagenAInsertar.EsquinaReferencia == EsquinaEnum.INFERIOR_IZQUIERDA) + { + if (anguloImgPag == 270) + { + medidas.ancho = image.GetImageScaledHeight(); + } + else if (anguloImgPag != 180) + { + medidas.alto = image.GetImageScaledHeight(); + + } + else + { + medidas.ancho = image.GetImageScaledWidth(); + } + } + + if (ImagenAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_IZQUIERDA) + { + if (anguloImgPag == 270) + { + medidas.alto = image.GetImageScaledWidth(); + medidas.ancho = image.GetImageScaledHeight(); + } + else if (anguloImgPag == 180) + { + medidas.alto = image.GetImageScaledHeight(); + medidas.ancho = image.GetImageScaledWidth(); + } + } + + if (ImagenAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_DERECHA) + { + if (anguloImgPag == 270) + { + medidas.alto = image.GetImageScaledWidth(); + } + else if (anguloImgPag == 180) + { + medidas.alto = image.GetImageScaledHeight(); + } + } + break; + case 270: + if (ImagenAInsertar.EsquinaReferencia == EsquinaEnum.INFERIOR_IZQUIERDA || ImagenAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_IZQUIERDA) + { + if (anguloImgPag == 360) + { + medidas.alto = image.GetImageScaledHeight(); + medidas.ancho = image.GetImageScaledWidth(); + } + else + { + medidas.alto = image.GetImageScaledHeight(); + } + } + else + { + if (ImagenAInsertar.EsquinaReferencia == EsquinaEnum.SUPERIOR_DERECHA) + { + medidas.ancho = image.GetImageScaledWidth(); + if (ImagenAInsertar.AnguloRotacion == 0) + { + medidas.alto = image.GetImageScaledHeight(); + } + } + else if (ImagenAInsertar.AnguloRotacion != 90) + { + medidas.ancho = image.GetImageScaledWidth(); + medidas.alto = image.GetImageScaledHeight(); + } + } + break; + } + + return medidas; + } + public static float PuntosAMilimetros(float puntos) + { + float valorPunto = 2.83465f; + return puntos / valorPunto; + } + public static float comprobarEjeXPagRotada(float ejeX, float ejeY, float angulo) + { + float resultado = 0; + + if (angulo == 90 || angulo == 270) + { + ejeX = ejeY; + } + + resultado = ejeX; + + return resultado; + } + public static float comprobarEjeYPagRotada(float ejeX, float ejeY, float angulo) + { + float resultado = 0; + + if (angulo == 90 || angulo == 270) + { + ejeY = ejeX; + } + + resultado = ejeY; + + return resultado; + } + public static float comprobarAncho(float ancho, float alto, float angulo) + { + float resultado = 0; + + if (angulo == 90 || angulo == 270) + { + ancho = alto; + } + resultado = ancho; + + return resultado; + } + public static float comprobarAlto(float ancho, float alto, float angulo) + { + float resultado = 0; + + if (angulo == 90 || angulo == 270) + { + alto = ancho; + } + + resultado = alto; + + return resultado; + } + public async static Task SellaPDFAsync(string PdfOrigen, string PdfDestino, List TextosAInsertar, List ImagenesAInsertar, DelegadoProgreso dlgpr = null, CancellationTokenSource cts = null, bool ConvertirPDFA = false, bool PDFAObligatorio=false) + { + FileStream fsR = new FileStream(PdfOrigen, FileMode.Open, FileAccess.Read, FileShare.Read); + FileStream fsW = new FileStream(PdfDestino, FileMode.Create, FileAccess.Write, FileShare.None); + Task t; + t = SellaPDFAsync(fsR, fsW, TextosAInsertar, ImagenesAInsertar, dlgpr, cts,ConvertirPDFA,PDFAObligatorio); + await t; + return t.Result; + } + public static void SellaPDF(string PdfOrigen, string PdfDestino, List TextosAInsertar, List ImagenesAInsertar, DelegadoProgreso dlgpr = null, bool ConvertirPDFA = false, bool PDFAObligatorio=false) + { + + + FileStream fsR = new FileStream(PdfOrigen, FileMode.Open, FileAccess.Read, FileShare.Read); + FileStream fsW = new FileStream(PdfDestino, FileMode.Create, FileAccess.Write, FileShare.None); + SellaPDF(fsR, fsW, TextosAInsertar, ImagenesAInsertar, dlgpr,ConvertirPDFA:ConvertirPDFA,PDFAObligatorio:PDFAObligatorio); + } + public static async Task SellaPDFAsync(Stream PdfOrigen, List TextosAInsertar, List ImagenesAInsertar, DelegadoProgreso dlgpr = null, CancellationTokenSource cts = null, bool ConvertirPDFA=false) + { + + MemoryStream ms = new MemoryStream(); + await Task.Factory.StartNew(() => SellaPDFAsync(PdfOrigen, ms, TextosAInsertar, ImagenesAInsertar, dlgpr, cts, ConvertirPDFA: ConvertirPDFA)); + return ms; + } + public static Stream SellaPDF(Stream PdfOrigen, List TextosAInsertar, List ImagenesAInsertar, DelegadoProgreso dlgpr = null, bool ConvertirPDFA = false, bool PDFAObligatorio=false) + { + + MemoryStream ms = new MemoryStream(); + SellaPDF(PdfOrigen, ms, TextosAInsertar, ImagenesAInsertar, dlgpr,ConvertirPDFA: ConvertirPDFA, PDFAObligatorio: PDFAObligatorio); + return ms; + } + public static async Task SellaPDFAsync(String PdfOrigen, List TextosAInsertar, List ImagenesAInsertar, DelegadoProgreso dlgpr = null, CancellationTokenSource cts = null, bool ConvertirPDFA = false, bool PDFAObligatorio=false) + { + + MemoryStream ms = new MemoryStream(); + MemoryStream mso = new MemoryStream(System.IO.File.ReadAllBytes(PdfOrigen)); + await Task.Factory.StartNew(() => SellaPDFAsync(mso, ms, TextosAInsertar, ImagenesAInsertar, dlgpr, cts, ConvertirPDFA: ConvertirPDFA, PDFAObligatorio: PDFAObligatorio)); + return ms; + } + public static Stream SellaPDF(String PdfOrigen, List TextosAInsertar, List ImagenesAInsertar, DelegadoProgreso dlgpr = null, bool ConvertirPDFA = false, bool PDFAObligatorio=false) + { + + + MemoryStream ms = new MemoryStream(); + MemoryStream mso = new MemoryStream(System.IO.File.ReadAllBytes(PdfOrigen)); + SellaPDF(mso, ms, TextosAInsertar, ImagenesAInsertar, dlgpr, ConvertirPDFA: ConvertirPDFA, PDFAObligatorio: PDFAObligatorio); + return ms; + } + public static float calcularDistanciaX(int esquina, Rectangle tamanoPagina, float coordenada, float anchoImagen, float altoImagen, float anguloImagen, float anguloPagina, Rectangle cropBoxPagina) + { + float distanciaX = 0; + + float anchoImagenOrig = anchoImagen; + float altoImagenOrig = altoImagen; + + float diferenciaAncho = 0; + float diferenciaAlto = 0; + + if (tamanoPagina.GetWidth() != cropBoxPagina.GetWidth()) + { + diferenciaAncho = tamanoPagina.GetWidth() - cropBoxPagina.GetWidth(); + } + else + { + diferenciaAncho = cropBoxPagina.GetWidth(); + } + + if (tamanoPagina.GetHeight() != cropBoxPagina.GetHeight()) + { + diferenciaAlto = tamanoPagina.GetHeight() - cropBoxPagina.GetHeight(); + } + else + { + diferenciaAlto = cropBoxPagina.GetHeight(); + } + + switch (esquina) + { + case 0: + switch (anguloImagen) + { + case 90: + if (anguloPagina == 90) + { + distanciaX = cropBoxPagina.GetX() + diferenciaAncho - (PuntosAMilimetros(coordenada) + altoImagen); + } + else + { + distanciaX = cropBoxPagina.GetX() + PuntosAMilimetros(coordenada); + } + break; + + case 180: + if (anguloPagina == 90) + { + distanciaX = cropBoxPagina.GetX() + diferenciaAncho - (PuntosAMilimetros(coordenada) + anchoImagenOrig); + } + else if (anguloPagina == 180) + { + distanciaX = cropBoxPagina.GetX() + diferenciaAncho - (PuntosAMilimetros(coordenada) + anchoImagen); + } + else + { + distanciaX = cropBoxPagina.GetX() + PuntosAMilimetros(coordenada); + } + break; + + case 270: + if (anguloPagina == 180) + { + distanciaX = cropBoxPagina.GetX() + diferenciaAncho - (PuntosAMilimetros(coordenada) + anchoImagen); + } + else + { + distanciaX = cropBoxPagina.GetX() + PuntosAMilimetros(coordenada) + altoImagen; + } + break; + + case 360: + distanciaX = cropBoxPagina.GetX() + (PuntosAMilimetros(coordenada)); + break; + + default: + distanciaX = cropBoxPagina.GetX() + PuntosAMilimetros(coordenada) - anchoImagenOrig; + break; + } + break; + + case 1: + switch (anguloImagen) + { + case 0: + distanciaX = (diferenciaAncho + cropBoxPagina.GetX()) - (anchoImagenOrig + PuntosAMilimetros(coordenada)); + break; + case 90: + + if (anguloPagina == 90) + { + distanciaX = (diferenciaAncho + cropBoxPagina.GetX()) - (altoImagenOrig + PuntosAMilimetros(coordenada)); + } + else + { + distanciaX = (diferenciaAncho + cropBoxPagina.GetX()) - (altoImagenOrig + PuntosAMilimetros(coordenada)); + } + + break; + + case 180: + anchoImagen = altoImagen; + + if (anguloPagina == 90) + { + + distanciaX = (diferenciaAncho + cropBoxPagina.GetX()) - (anchoImagenOrig + PuntosAMilimetros(coordenada)); + } + else if (anguloPagina == 180) + { + distanciaX = cropBoxPagina.GetX() + (PuntosAMilimetros(coordenada) - anchoImagenOrig); + } + else + { + distanciaX = (diferenciaAncho + cropBoxPagina.GetX()) - (anchoImagen + PuntosAMilimetros(coordenada)); + } + break; + + case 270: + if (anguloPagina == 270) + { + distanciaX = (PuntosAMilimetros(coordenada) + cropBoxPagina.GetX()); + } + else if (anguloPagina == 180) + { + distanciaX = cropBoxPagina.GetX() + (PuntosAMilimetros(coordenada) - altoImagen); + } + break; + case 360: + distanciaX = (PuntosAMilimetros(coordenada + anchoImagenOrig) + cropBoxPagina.GetX()); + break; + + default: + distanciaX = (diferenciaAncho + tamanoPagina.GetX()) - (anchoImagen + PuntosAMilimetros(coordenada)); + + break; + } + break; + + case 2: + switch (anguloImagen) + { + case 90: + if (anguloPagina == 90) + { + if (PuntosAMilimetros(coordenada) < altoImagenOrig) + { + distanciaX = cropBoxPagina.GetX() + altoImagenOrig - PuntosAMilimetros(coordenada); + } + else + { + distanciaX = cropBoxPagina.GetX() + PuntosAMilimetros(coordenada) - altoImagenOrig; + } + } + else + { + distanciaX = cropBoxPagina.GetX() + PuntosAMilimetros(coordenada); + } + + break; + case 180: + if (anguloPagina == 180) + { + distanciaX = cropBoxPagina.GetX() + diferenciaAncho - (PuntosAMilimetros(coordenada) + anchoImagen); + } + else + { + distanciaX = cropBoxPagina.GetX() + PuntosAMilimetros(coordenada) - anchoImagenOrig; + } + break; + case 270: + if (anguloPagina == 180) + { + + distanciaX = cropBoxPagina.GetX() + diferenciaAncho - (PuntosAMilimetros(coordenada) + anchoImagen); + } + else + { + distanciaX = (diferenciaAncho + cropBoxPagina.GetX()) - (altoImagenOrig + PuntosAMilimetros(coordenada)); + } + break; + case 360: + distanciaX = (diferenciaAncho + cropBoxPagina.GetX()) - (anchoImagenOrig + PuntosAMilimetros(coordenada)); + break; + default: + distanciaX = (PuntosAMilimetros(coordenada) + anchoImagenOrig) + cropBoxPagina.GetX(); + break; + } + break; + + case 3: + switch (anguloImagen) + { + case 90: + if (anguloPagina == 90) + { + if (PuntosAMilimetros(coordenada) > altoImagenOrig) + { + distanciaX = cropBoxPagina.GetX() + (PuntosAMilimetros(coordenada)); + } + else + { + distanciaX = cropBoxPagina.GetX() + (altoImagenOrig + PuntosAMilimetros(coordenada)); + } + } + else if (anguloPagina == 0) + { + distanciaX = cropBoxPagina.GetX() + diferenciaAncho - (altoImagenOrig + PuntosAMilimetros(coordenada)); + } + break; + + case 180: + + if (anguloPagina == 180) + { + distanciaX = cropBoxPagina.GetX() + (PuntosAMilimetros(coordenada) - anchoImagen); + } + else + { + if (PuntosAMilimetros(coordenada) > anchoImagenOrig) + { + distanciaX = cropBoxPagina.GetX() + (PuntosAMilimetros(coordenada) - anchoImagenOrig); + } + else + { + distanciaX = cropBoxPagina.GetX() + (anchoImagenOrig - PuntosAMilimetros(coordenada)); + } + } + + break; + + case 270: + if (anguloPagina == 180) + { + distanciaX = cropBoxPagina.GetX() + (PuntosAMilimetros(coordenada) - anchoImagen); + } + else + { + distanciaX = cropBoxPagina.GetX() + diferenciaAncho - (altoImagen + PuntosAMilimetros(coordenada)); + } + break; + + case 360: + distanciaX = cropBoxPagina.GetX() + diferenciaAncho - (anchoImagenOrig + PuntosAMilimetros(coordenada)); + break; + + default: + distanciaX = cropBoxPagina.GetX() + diferenciaAncho - (anchoImagen + PuntosAMilimetros(coordenada)); + break; + } + break; + } + + return distanciaX; + } + public static float calcularDistanciaY(int esquina, Rectangle tamanoPagina, float coordenada, float anchoImagen, float altoImagen, float anguloImagen, float anguloPagina, Rectangle cropBoxPagina) + { + float distanciaY = 0; + float anchoImagenOrig = anchoImagen; + float altoImagenOrig = altoImagen; + + float diferenciaAncho = 0; + float diferenciaAlto = 0; + + if (tamanoPagina.GetWidth() != cropBoxPagina.GetWidth()) + { + diferenciaAncho = tamanoPagina.GetWidth() - cropBoxPagina.GetWidth(); + } + else + { + diferenciaAncho = cropBoxPagina.GetWidth(); + } + + if (tamanoPagina.GetHeight() != cropBoxPagina.GetHeight()) + { + diferenciaAlto = tamanoPagina.GetHeight() - cropBoxPagina.GetHeight(); + } + else + { + diferenciaAlto = cropBoxPagina.GetHeight(); + } + switch (esquina) + { + case 0: + switch (anguloImagen) + { + case 90: + if (anguloPagina == 90) + { + distanciaY = cropBoxPagina.GetY() + PuntosAMilimetros(coordenada) - anchoImagen; + } + else + { + distanciaY = cropBoxPagina.GetY() + PuntosAMilimetros(coordenada); + } + break; + + case 180: + if (anguloPagina == 90) + { + distanciaY = cropBoxPagina.GetY() + PuntosAMilimetros(coordenada) + altoImagenOrig; + } + else if (anguloPagina == 180) + { + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (altoImagen + PuntosAMilimetros(coordenada)); + } + else + + { + distanciaY = cropBoxPagina.GetY() + PuntosAMilimetros(coordenada); + } + break; + + case 270: + if (anguloPagina == 180) + { + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (altoImagen + PuntosAMilimetros(coordenada)); + } + else + { + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (altoImagen + PuntosAMilimetros(coordenada)); + } + break; + + case 360: + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (altoImagen + PuntosAMilimetros(coordenada)); + break; + default: + distanciaY = cropBoxPagina.GetY() + PuntosAMilimetros(coordenada); + break; + } + break; + + case 1: + switch (anguloImagen) + { + case 90: + if (anguloPagina == 90) + { + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (anchoImagenOrig + PuntosAMilimetros(coordenada)); + } + else + { + + distanciaY = cropBoxPagina.GetY() + PuntosAMilimetros(coordenada); + } + break; + case 180: + + if (anguloPagina == 90) + { + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (PuntosAMilimetros(coordenada)); + } + else if (anguloPagina == 180) + { + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (PuntosAMilimetros(coordenada) + altoImagenOrig); + } + else + { + distanciaY = cropBoxPagina.GetY() + (anchoImagenOrig + PuntosAMilimetros(coordenada)); + } + + break; + case 270: + if (anguloPagina == 270) + { + if ((PuntosAMilimetros(coordenada) < anchoImagenOrig)) + { + distanciaY = cropBoxPagina.GetY() + anchoImagenOrig + (PuntosAMilimetros(coordenada)); + } + else + { + distanciaY = cropBoxPagina.GetY() + (PuntosAMilimetros(coordenada) - anchoImagenOrig); + } + + } + else if (anguloPagina == 180) + { + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (PuntosAMilimetros(coordenada) + anchoImagen); + + } + break; + case 360: + distanciaY = cropBoxPagina.GetY() + (PuntosAMilimetros(coordenada) - altoImagenOrig); + break; + + default: + distanciaY = PuntosAMilimetros(coordenada) + cropBoxPagina.GetY(); + break; + } + break; + + case 2: + + switch (anguloImagen) + { + case 90: + if (anguloPagina == 90) + { + distanciaY = cropBoxPagina.GetY() + (PuntosAMilimetros(coordenada) - anchoImagenOrig); + } + else + { + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (anchoImagenOrig + PuntosAMilimetros(coordenada)); + } + break; + + case 180: + if (anguloPagina == 180) + { + distanciaY = cropBoxPagina.GetY() + (PuntosAMilimetros(coordenada) + altoImagen); + } + else + { + distanciaY = cropBoxPagina.GetY() + (altoImagenOrig + PuntosAMilimetros(coordenada)); + } + break; + case 270: + if (anguloPagina == 180) + { + distanciaY = cropBoxPagina.GetY() + (PuntosAMilimetros(coordenada) + altoImagen); + } + else + { + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (PuntosAMilimetros(coordenada)); + } + break; + + case 360: + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (altoImagenOrig + PuntosAMilimetros(coordenada)); + + break; + default: + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (altoImagenOrig + PuntosAMilimetros(coordenada)); + break; + } + + break; + + case 3: + switch (anguloImagen) + { + case 90: + altoImagen = anchoImagen; + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (altoImagen + PuntosAMilimetros(coordenada)); + break; + case 180: + if (anguloPagina == 180) + { + distanciaY = cropBoxPagina.GetY() + PuntosAMilimetros(coordenada) + altoImagen; + } + else + { + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (PuntosAMilimetros(coordenada)); + } + break; + case 270: + if (anguloPagina == 180) + { + distanciaY = cropBoxPagina.GetY() + PuntosAMilimetros(coordenada) + altoImagen; + } + else + { + distanciaY = PuntosAMilimetros(coordenada) > anchoImagenOrig ? cropBoxPagina.GetY() + PuntosAMilimetros(coordenada) - anchoImagenOrig : cropBoxPagina.GetY() + anchoImagenOrig + PuntosAMilimetros(coordenada); + } + break; + case 360: + distanciaY = PuntosAMilimetros(coordenada) > altoImagenOrig ? cropBoxPagina.GetY() + PuntosAMilimetros(coordenada) - altoImagenOrig : cropBoxPagina.GetY() + altoImagenOrig - PuntosAMilimetros(coordenada); + break; + default: + distanciaY = cropBoxPagina.GetY() + diferenciaAlto - (altoImagen + PuntosAMilimetros(coordenada)); + break; + } + break; + } + + return distanciaY; + } + public static string devolverDatosFecha(DateTime fecha, string datoADevolver) + { + + string datoFecha = string.Empty; + + + + switch (datoADevolver.ToUpper()) + { + + case "DIA": + + datoFecha = fecha.ToString("dd/MM/yyyy").Split('/')[0]; + + break; + + case "MES": + + datoFecha = fecha.ToString("MMMM").ToUpper(); + + + break; + + case "AÑO": + + datoFecha = fecha.ToString("dd/MM/yyyy").Split('/')[2]; + break; + + } + return datoFecha; + } + public static string devolverDatos(string cadenaDatos, int seleccionarDato) + { + string datos = string.Empty; + + switch (seleccionarDato) + { + + case 1: + + datos = cadenaDatos.Split(' ')[0]; + + break; + + case 2: + + datos = cadenaDatos.Split(' ')[1]; + + break; + + + case 3: + + datos = cadenaDatos.Split(' ')[2]; + + break; + } + + + + return datos; + + + } + private static TextAlignment devolverAlineamiento(int alineamiento) + { + TextAlignment obtenerAlineamiento = new TextAlignment(); + + switch (alineamiento) + { + case 0: + obtenerAlineamiento = TextAlignment.LEFT; + break; + + case 1: + obtenerAlineamiento = TextAlignment.RIGHT; + break; + + case 2: + obtenerAlineamiento = TextAlignment.CENTER; + break; + + case 3: + obtenerAlineamiento = TextAlignment.JUSTIFIED_ALL; + break; + } + + return obtenerAlineamiento; + + } + private static TSpdf.Kernel.Font.PdfFont devolverTipoFuente(string nombreFuente) + { + + TSpdf.Kernel.Font.PdfFont tipoFuente; + switch (nombreFuente) + { + case "COURIER": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.COURIER); + break; + + case "COURIER_BOLD": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.COURIER_BOLD); + break; + + case "COURIER_BOLDOBLIQUE": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.COURIER_BOLDOBLIQUE); + break; + + case "COURIER_OBLIQUE": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.COURIER_OBLIQUE); + break; + + case "HELVETICA": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.HELVETICA); + break; + + case "HELVETICA_BOLD": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLD); + break; + + case "HELVETICA_BOLDOBLIQUE": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLDOBLIQUE); + break; + + case "HELVETICA_OBLIQUE": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_OBLIQUE); + break; + + case "SYMBOL": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.SYMBOL); + break; + + case "TIMES_ROMAN": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN); + break; + + case "TIMES_BOLD": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_BOLD); + break; + + case "TIMES_BOLDITALIC": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_BOLDITALIC); + break; + + case "TIMES_ITALIC": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_ITALIC); + break; + + case "ZAPFDINGBATS": + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.ZAPFDINGBATS); + break; + + default: + tipoFuente = PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN); + break; + } + + return tipoFuente; + } + public delegate void DelegadoProgreso(Progreso progreso); + public class Progreso + { + public int NumeroPaginasTotal { get; set; } + public int NumeroPaginaActual { get; set; } + public string Estado { get; set; } + } + public class medidasTextoImagen + { + public float ancho { get; set; } + public float alto { get; set; } + } + + } +} \ No newline at end of file diff --git a/tsPDFUtilsCore/TextoEnPdf.cs b/tsPDFUtilsCore/TextoEnPdf.cs new file mode 100644 index 0000000..3a639e6 --- /dev/null +++ b/tsPDFUtilsCore/TextoEnPdf.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace tsPDFUtilsCore +{ + public class TextoEnPdf + { + public string Texto { get; set; } + public FuenteEnum Fuente { get; set; } + public float TamañoFuente { get; set; } + public System.Drawing.Color Color { get; set; } + public double Transparencia { get; set; } + public EsquinaEnum EsquinaReferencia { get; set; } + public float AnguloRotacion { get; set; } + public AlineamientoEnum Alineamiento { get; set; } + public float CoordenadaX { get; set; } + public float CoordenadaY { get; set; } + public int PaginaInicio { get; set; } + public int PaginaFin { get; set; } + public float anchoTexto { get; set; } + public float altoTexto { get; set; } + public TipoTextoEnum TipoTexto { get; set; } + } +} diff --git a/tsPDFUtilsCore/Utilidades.cs b/tsPDFUtilsCore/Utilidades.cs new file mode 100644 index 0000000..49fe50a --- /dev/null +++ b/tsPDFUtilsCore/Utilidades.cs @@ -0,0 +1,158 @@ +using System.IO; +using TSpdf.Kernel.Pdf; +using TSpdf.Kernel.Utils; +using TSpdf.Pdfa; + +namespace tsPDFUtilsCore +{ + public class Utilidades + { + public static MemoryStream crearPDFA(Stream pdfOrigen) + { + var reader = new PdfReader(pdfOrigen); + MemoryStream ms = new MemoryStream(); + + var writerProps = new WriterProperties(); + writerProps.SetPdfVersion(PdfVersion.PDF_2_0); + writerProps.UseSmartMode(); + var writer = new PdfWriter(ms, writerProps); + writer.SetSmartMode(true); + + // Necesario para que el memoryStream no se cierre, y no de una execpción donde se llame a la función + writer.SetCloseStream(false); + + var sourcePDF = new PdfDocument(reader); + + bool esRGB = recorrerPaginas(sourcePDF); + + // Se recorre las páginas para comprobar que no falte la anotación F, en tal caso la mete + for (int page = 1; page <= sourcePDF.GetNumberOfPages(); page++) + { + var paginaActual = sourcePDF.GetPage(page); + var anotaciones = paginaActual.GetAnnotations(); + + if (anotaciones != null) + { + foreach (var anotacionActual in anotaciones) + { + var diccionario = anotacionActual.GetPdfObject(); + + if (!diccionario.ContainsKey(PdfName.F)) + { + diccionario.Put(PdfName.F, new PdfNumber(4)); + } + } + } + } + + PdfADocument disPDF; + + if (esRGB) + { + disPDF = new PdfADocument(writer, PdfAConformanceLevel.PDF_A_3B, new PdfOutputIntent("Custom" + , "", "https://www.color.org", "sRGB", new MemoryStream(Properties.Resources.sRGB2014))); + } + else + { + disPDF = new PdfADocument(writer, PdfAConformanceLevel.PDF_A_3B, new PdfOutputIntent("Custom" + , "", "https://www.color.org", "FOGRA39", new MemoryStream(Properties.Resources.fogra39L))); + + } + + disPDF.InitializeOutlines(); + + // Configurar parámetros requeridos + disPDF.SetTagged(); + disPDF.GetCatalog().SetLang(new PdfString("es-ES")); + disPDF.GetCatalog().SetViewerPreferences(new PdfViewerPreferences().SetDisplayDocTitle(true)); + + var merger = new PdfMerger(disPDF, true,true); + merger.Merge(sourcePDF, 1, sourcePDF.GetNumberOfPages()); + + + sourcePDF.Close(); + disPDF.Close(); + reader.Close(); + writer.Close(); + + // Esto hace falta para volver al inicio del memorystream + ms.Position = 0; + + return ms; + } + + static bool controlarInterpolate(PdfDictionary resources, bool tieneRGB) + { + + var xObjects = resources?.GetAsDictionary(PdfName.XObject); + bool tieneRGBActual = tieneRGB; + + foreach (var key in xObjects.KeySet()) + { + var stream = xObjects.GetAsStream(key); + + // se mira que tenga el prefijo subtype qye es la forma como el pdf pone las imagenes y los formularios -> /Subtype /Image ó /Subtype /Form + var subtype = stream.GetAsName(PdfName.Subtype); + + // En caso de que sea una imagen, pone el interpolate a false, que es lo que causaba el fallo en alguno de los pdfs + if (PdfName.Image.Equals(subtype)) + { + if (stream.GetAsBoolean(PdfName.Interpolate)?.GetValue() == true) + { + stream.Put(PdfName.Interpolate, PdfBoolean.FALSE); + } + + // Detectar RGB + var colorSpace = stream.Get(PdfName.ColorSpace); + + if (colorSpace != null && colorSpace.Equals(PdfName.DeviceRGB)) + { + tieneRGBActual = true; + tieneRGB = true; + } + + } + + // En caso de que sea un formulario, vuelve a llamar al metodo para buscar imagenes dentro + else if (PdfName.Form.Equals(subtype)) + { + var formRes = stream.GetAsDictionary(PdfName.Resources); + if (formRes != null) + { + bool hijoTieneRGB = controlarInterpolate(formRes, tieneRGBActual); + + if (hijoTieneRGB) + { + tieneRGBActual = true; + } + } + } + + } + + return tieneRGBActual; + } + + static bool recorrerPaginas(PdfDocument pdf) + { + bool tieneRGB = false; + + for (int i = 1; i <= pdf.GetNumberOfPages(); i++) + { + bool rgbMinimo = false; + + var diccPaginaActual = pdf.GetPage(i).GetResources().GetPdfObject(); + rgbMinimo = controlarInterpolate(diccPaginaActual, tieneRGB); + + if (rgbMinimo) + { + tieneRGB = true; + } + } + + return tieneRGB; + } + + + } +} diff --git a/tsPDFUtilsCore/tsPDFUtilsCore.csproj b/tsPDFUtilsCore/tsPDFUtilsCore.csproj new file mode 100644 index 0000000..0a34bf8 --- /dev/null +++ b/tsPDFUtilsCore/tsPDFUtilsCore.csproj @@ -0,0 +1,60 @@ + + + + SAK + SAK + SAK + SAK + + + + netstandard2.0;net48 + 1.0.16 + 1.0.16 + Library + tsPDFUtilsCore + tsPDFUtilsCore + netstandard2.0, libreria + 1.0.16 + Manuel, Perea + Tecnosis S.A + Utilidades de tratamiento de pdf, de firmas digitales e imagenes. + + - 1.0.16 2026-05-20 Correcciones dependencias Microsoft.Extensions.Logging + - 1.0.12 2025-10-23 se posiciona en 0 la posicion del stream original + - 1.0.10 2025-10-23 Se relanzan exepciones en caso de error + - 1.0.9 2025-10-23 Se añade parámetro para en caso de pdf/a3b poder decidir si es obligatorio o no + - 1.0.8 2025-10-06 Se añade parámetro para crear el standard pdf/a3b + - 1.0.7 2025-10-06 Se deja solo net standard + - 1.0.6 2025-10-06 Actualización de dependencias microsoft.extensions.options + - 1.0.5 Actualización de dependencias microsoft.extensions.options + - 1.0.4 Corrección en TSpdf.io + - Primera versión estable. + - Probando Nuget + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + +