Imports System.Security.Cryptography.X509Certificates Imports System.Text.RegularExpressions Imports Newtonsoft.Json Namespace Models Public Class CertificateLoginBridgeOptions Public Property PublicUrl As String = String.Empty Public Property ForwardedCertificateHeader As String = "X-ARR-ClientCert" Public Property AdditionalForwardedCertificateHeaders As List(Of String) = New List(Of String)() Public Property AllowedParentOrigins As List(Of String) = New List(Of String)() Public Shared Function LoadFromConfiguration() As CertificateLoginBridgeOptions Dim publicUrl = FirstNonEmpty( ConfigurationManager.AppSettings("CertificateLoginPublicUrl"), ConfigurationManager.AppSettings("UrlCertLogin")) Dim forwardedCertificateHeader = FirstNonEmpty( ConfigurationManager.AppSettings("CertificateForwardedHeader"), ConfigurationManager.AppSettings("CertHeaderName"), "X-ARR-ClientCert") Dim additionalHeadersSetting = FirstNonEmpty( ConfigurationManager.AppSettings("CertificateAdditionalForwardedHeaders"), ConfigurationManager.AppSettings("AdditionalCertHeaders")) Dim allowedOriginsSetting = FirstNonEmpty( ConfigurationManager.AppSettings("CertificateAllowedParentOrigins"), ConfigurationManager.AppSettings("AllowedParentOrigins")) Return New CertificateLoginBridgeOptions With { .PublicUrl = publicUrl, .ForwardedCertificateHeader = forwardedCertificateHeader, .AdditionalForwardedCertificateHeaders = SplitSetting(additionalHeadersSetting), .AllowedParentOrigins = SplitSetting(allowedOriginsSetting) } End Function Private Shared Function SplitSetting(value As String) As List(Of String) If String.IsNullOrWhiteSpace(value) Then Return New List(Of String)() End If Return value.Split(New Char() {";"c, "|"c}, StringSplitOptions.RemoveEmptyEntries). Select(Function(item) item.Trim()). Where(Function(item) item <> String.Empty). ToList() End Function Private Shared Function FirstNonEmpty(ParamArray values() As String) As String For Each value In values If Not String.IsNullOrWhiteSpace(value) Then Return value.Trim() End If Next Return String.Empty End Function End Class Public Class CertificateProxyLoginResponse Public Property Token As String = String.Empty Public Property User As CertificateProxyUser End Class Public Class CertificateProxyUser Public Property NIF As String = String.Empty Public Property NOMBRE As String = String.Empty Public Property APELLIDOS As String = String.Empty Public Property ADMINISTRARPTYREGISTRO As Boolean? End Class Public Module CertificateLoginBridgeHelper Private ReadOnly DefaultCertificateHeaders As String() = { "X-ARR-ClientCert", "X-Client-Cert", "X-Client-Cert-Der-Base64", "X-Client-Certificate", "X-Client-Certificate-Der-Base64", "X-SSL-CERT", "X-SSL-Client-Cert", "X-SSL-Client-Cert-Base64", "X-Tls-Client-Cert", "X-Tls-Client-Cert-Der-Base64", "X-Forwarded-Tls-Client-Cert", "Ssl-Client-Cert" } Private ReadOnly DefaultCertificateServerVariables As String() = { "SSL_CLIENT_CERT", "HTTP_SSL_CLIENT_CERT", "HTTP_X_SSL_CLIENT_CERT", "HTTP_X_CLIENT_CERT", "HTTP_X_CLIENT_CERTIFICATE", "HTTP_X_ARR_CLIENTCERT", "CERT_CERTIFICATE" } Public Function IsCertificateHostRequest(request As HttpRequest, publicUrl As String) As Boolean If request Is Nothing OrElse String.IsNullOrWhiteSpace(publicUrl) Then Return False End If Dim configuredUri As Uri = Nothing If Not Uri.TryCreate(publicUrl, UriKind.Absolute, configuredUri) Then Return False End If Dim forwardedHostRaw = GetFirstHeaderValue(request, "X-Forwarded-Host") Dim forwardedProtoRaw = GetFirstHeaderValue(request, "X-Forwarded-Proto") Dim forwardedPortRaw = GetFirstHeaderValue(request, "X-Forwarded-Port") Dim hasForwardedInfo = Not String.IsNullOrWhiteSpace(forwardedHostRaw) OrElse Not String.IsNullOrWhiteSpace(forwardedProtoRaw) OrElse Not String.IsNullOrWhiteSpace(forwardedPortRaw) Dim requestHost = ExtractHost(If(hasForwardedInfo, forwardedHostRaw, Nothing)) If String.IsNullOrWhiteSpace(requestHost) AndAlso request.Url IsNot Nothing Then requestHost = request.Url.Host End If If Not String.Equals(requestHost, configuredUri.Host, StringComparison.OrdinalIgnoreCase) Then Return False End If If Not hasForwardedInfo Then Return True End If Dim requestScheme = FirstToken(forwardedProtoRaw) If String.IsNullOrWhiteSpace(requestScheme) Then requestScheme = configuredUri.Scheme End If Dim requestPort = ParseNullableInt(forwardedPortRaw) If Not requestPort.HasValue Then requestPort = ExtractPort(forwardedHostRaw) End If If Not requestPort.HasValue Then requestPort = GetDefaultPort(requestScheme) End If Dim configuredPort = If(configuredUri.IsDefaultPort, GetDefaultPort(configuredUri.Scheme), configuredUri.Port) Return String.Equals(requestScheme, configuredUri.Scheme, StringComparison.OrdinalIgnoreCase) AndAlso requestPort.Value = configuredPort End Function Public Function IsAllowedParentOrigin(parentOrigin As String, allowedParentOrigins As IEnumerable(Of String)) As Boolean Dim normalizedParentOrigin = NormalizeOrigin(parentOrigin) If String.IsNullOrWhiteSpace(normalizedParentOrigin) Then Return False End If Return allowedParentOrigins. Where(Function(origin) Not String.IsNullOrWhiteSpace(origin)). Select(Function(origin) NormalizeOrigin(origin)). Any(Function(origin) String.Equals(origin, normalizedParentOrigin, StringComparison.OrdinalIgnoreCase)) End Function Public Function ReadCertificate( request As HttpRequest, forwardedCertificateHeader As String, additionalForwardedCertificateHeaders As IEnumerable(Of String)) As X509Certificate2 Dim directCertificate = ReadDirectCertificate(request) If directCertificate IsNot Nothing Then Return directCertificate End If Dim headerCandidates As New List(Of String)() AddHeaderCandidate(headerCandidates, forwardedCertificateHeader) If additionalForwardedCertificateHeaders IsNot Nothing Then For Each headerName In additionalForwardedCertificateHeaders AddHeaderCandidate(headerCandidates, headerName) Next End If For Each headerName In DefaultCertificateHeaders AddHeaderCandidate(headerCandidates, headerName) Next For Each headerName In headerCandidates.Distinct(StringComparer.OrdinalIgnoreCase) Dim rawHeader = request.Headers(headerName) If String.IsNullOrWhiteSpace(rawHeader) Then Continue For End If Dim certificate = TryCreateCertificate(rawHeader) If certificate IsNot Nothing Then Return certificate End If Next For Each serverVariableName In DefaultCertificateServerVariables Dim rawValue = request.ServerVariables(serverVariableName) If String.IsNullOrWhiteSpace(rawValue) Then Continue For End If Dim certificate = TryCreateCertificate(rawValue) If certificate IsNot Nothing Then Return certificate End If Next Return Nothing End Function Public Function ObtenerDni(certificado As X509Certificate2) As String Try If certificado Is Nothing Then Return String.Empty End If Dim candidateValues As New List(Of String) From { ExtractDistinguishedNameValue(certificado.Subject, "OID.2.5.4.97"), ExtractDistinguishedNameValue(certificado.Subject, "2.5.4.97"), ExtractDistinguishedNameValue(certificado.Subject, "SERIALNUMBER"), ExtractDistinguishedNameValue(certificado.Subject, "OID.2.5.4.5"), ExtractDistinguishedNameValue(certificado.Subject, "2.5.4.5"), certificado.GetNameInfo(X509NameType.SimpleName, False) } For Each candidateValue In candidateValues Dim nif = NormalizarIdentificadorFiscal(candidateValue) If nif <> String.Empty Then Return nif End If Next Return NormalizarIdentificadorFiscal(certificado.Subject) Catch Return String.Empty End Try End Function Public Function BuildIframeDniHtml(dni As String, parentOrigin As String) As String Dim payloadJson = JsonConvert.SerializeObject(New With { .dni = dni }) Dim targetOriginJson = JsonConvert.SerializeObject(NormalizeOrigin(parentOrigin)) Return BuildIframePostMessageHtml( payloadJson, targetOriginJson, "Validando acceso con certificado...") End Function Public Function BuildIframeLoginHtml(response As CertificateProxyLoginResponse, parentOrigin As String) As String Dim payloadJson = JsonConvert.SerializeObject(New With { .token = response.Token, .user = response.User }) Dim targetOriginJson = JsonConvert.SerializeObject(NormalizeOrigin(parentOrigin)) Return BuildIframePostMessageHtml( payloadJson, targetOriginJson, "Validando acceso con certificado...") End Function Public Function BuildIframeErrorHtml(message As String, parentOrigin As String) As String Dim payloadJson = JsonConvert.SerializeObject(New With { .error = message }) Dim targetOriginJson = JsonConvert.SerializeObject(NormalizeOrigin(parentOrigin)) Dim safeMessage = If(String.IsNullOrWhiteSpace(message), "No se pudo completar el acceso con certificado.", message) Return BuildIframePostMessageHtml( payloadJson, targetOriginJson, safeMessage) End Function Private Function BuildIframePostMessageHtml( payloadJson As String, targetOriginJson As String, visibleMessage As String) As String Dim visibleMessageJson = JsonConvert.SerializeObject(visibleMessage) Return "" & "" & "