'---------------------------------------------------------------------------
' 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 = "<Causa de revocación> (CodRev=<N>)"  o  "Causa no disponible (CodRev=-1)"

        Indeterminado = 2   ' MsjEstadoRevoc = "<Causa de la no determinación (OCSP, comunicaciones, servidor...)>"

    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 = "", " <NoPresente>", vbCrLf & Indent(MsjEstadoRevoc, 8)) & vbCrLf
                    End If
                End If

                res &= "    Información de revocación válida desde: " & If(ThisUpdateUTC = Nothing, "<NoPresente>", DateToString(ThisUpdateUTC.ToLocalTime())) & vbCrLf &
                       "    Información de revocación válida hasta: " & If(NextUpdateUTC = Nothing, "<NoPresente>", 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': <NoPresente>"
                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, "<NoPresente>", 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, "<NoPresente>",
                                                                                                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 = "<Desconocido:" & sn & ">"
        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 "<LISTA NULA>"

        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 & "<LISTA NULA>"

        If a.Rank <> 1 Then Throw New Exception("ArrayToString: Array NO unidimensional.")

        If a.Length = 0 Then Return globalIndent & "<LISTA VACÍA>"

        '------

        ' 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 = "<Elemento Nulo>"
            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 <TYPENAME>) = 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
