Pregunta

Estoy intentando conectar con una API que utiliza un certificado SSL autofirmado. Estoy haciendo así que el uso de objetos HttpWebRequest y HttpWebResponse de .NET. Y me estoy poniendo una excepción que:

  

La conexión subyacente se cerró:. No se pudo establecer una relación de confianza para el SSL / TLS canal seguro

Yo entiendo lo que esto significa. Y entiendo ¿Por qué .NET siente que me debe advertir y cerrar la conexión. Pero en este caso, me gustaría simplemente conectarse a la API de todos modos, man-in-the-middle ser condenados ataques.

Entonces, ¿cómo hago para añadir una excepción para este certificado autofirmado? O es el enfoque que contar HttpWebRequest / respuesta no para validar el certificado en absoluto? ¿Cómo iba a hacer eso?

¿Fue útil?

Solución

@Domster: que funciona, pero es posible que desee hacer cumplir un poco de seguridad mediante la comprobación de si el hash del certificado coincide con lo que espera. Así que una versión ampliada se ve un poco como esto (basado en un código que estamos usando en vivo):

static readonly byte[] apiCertHash = { 0xZZ, 0xYY, ....};

/// <summary>
/// Somewhere in your application's startup/init sequence...
/// </summary>
void InitPhase()
{
    // Override automatic validation of SSL server certificates.
    ServicePointManager.ServerCertificateValidationCallback =
           ValidateServerCertficate;
}

/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this
/// validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the
/// remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote
/// certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified
/// certificate is accepted for authentication; true to accept or false to
/// reject.</returns>
private static bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        // Good certificate.
        return true;
    }

    log.DebugFormat("SSL certificate error: {0}", sslPolicyErrors);

    bool certMatch = false; // Assume failure
    byte[] certHash = cert.GetCertHash();
    if (certHash.Length == apiCertHash.Length)
    {
        certMatch = true; // Now assume success.
        for (int idx = 0; idx < certHash.Length; idx++)
        {
            if (certHash[idx] != apiCertHash[idx])
            {
                certMatch = false; // No match
                break;
            }
        }
    }

    // Return true => allow unauthenticated server,
    //        false => disallow unauthenticated server.
    return certMatch;
}

Otros consejos

Resulta que, si lo que desea desactivar la validación de certificados por completo, se puede cambiar el ServerCertificateValidationCallback en el ServicePointManager, así:

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

Esto validará todos los certificados (incluyendo inválida, expiró o los auto-firmado).

Tenga en cuenta, que en .NET 4.5 se puede anular la validación SSL por HttpWebRequest en sí (y no a través de delegado global que afecta a todas las solicitudes):

http://msdn.microsoft.com/ en-us / library / system.net.httpwebrequest.servercertificatevalidationcallback.aspx

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.ServerCertificateValidationCallback = delegate { return true; };

Agregue el auto firmado cert a las Autoridades de Certificación Raíz Trusted Computer locales

Puede importar el CERT ejecutando el MMC como administrador.

Cómo: Ver certificados con el complemento de MMC

El alcance de la devolución de llamada de validación utilizado en respuesta de Domster puede limitarse a una solicitud específica utilizando el parámetro de remitente en el delegado ServerCertificateValidationCallback. La siguiente clase de alcance sencilla utiliza esta técnica para conectar temporalmente una devolución de llamada de validación que sólo se ejecuta para una solicitud de objeto dado.

public class ServerCertificateValidationScope : IDisposable
{
    private readonly RemoteCertificateValidationCallback _callback;

    public ServerCertificateValidationScope(object request,
        RemoteCertificateValidationCallback callback)
    {
        var previous = ServicePointManager.ServerCertificateValidationCallback;
        _callback = (sender, certificate, chain, errors) =>
            {
                if (sender == request)
                {
                    return callback(sender, certificate, chain, errors);
                }
                if (previous != null)
                {
                    return previous(sender, certificate, chain, errors);
                }
                return errors == SslPolicyErrors.None;
            };
        ServicePointManager.ServerCertificateValidationCallback += _callback;
    }

    public void Dispose()
    {
        ServicePointManager.ServerCertificateValidationCallback -= _callback;
    }
}

La clase anterior se puede utilizar para ignorar todos los errores de certificado para una solicitud específica como sigue:

var request = WebRequest.Create(uri);
using (new ServerCertificateValidationScope(request, delegate { return true; }))
{
    request.GetResponse();
}

Para añadir una posible ayuda a otra persona ... Si usted quiere que pide al usuario para instalar el certificado autofirmado, puede utilizar este código (modificado de arriba).

No requiere derechos de administrador, instala a los usuarios locales de confianza perfiles:

    private static bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
        {
            // Good certificate.
            return true;
        }

        Common.Helpers.Logger.Log.Error(string.Format("SSL certificate error: {0}", sslPolicyErrors));
        try
        {
            using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
            {
                store.Open(OpenFlags.ReadWrite);
                store.Add(new X509Certificate2(cert));
                store.Close();
            }
            return true;
        }
        catch (Exception ex)
        {
            Common.Helpers.Logger.Log.Error(string.Format("SSL certificate add Error: {0}", ex.Message));
        }

        return false;
    }

Esto parece funcionar bien para nuestra aplicación, y si el usuario no presiona, la comunicación no va a funcionar.

Actualización: 11/12/2015 - El cambio de StoreName.Root a StoreName.My - Mi instalará en el almacén de usuarios locales, en lugar de la raíz. Raíz en algunos sistemas no funcionará, incluso si "Ejecutar como administrador"

Sólo partiendo de respuesta de devstuff para incluir comentarios sujeto y emisor de bienvenida ... ...

public class SelfSignedCertificateValidator
{
    private class CertificateAttributes
    {
        public string Subject { get; private set; }
        public string Issuer { get; private set; }
        public string Thumbprint { get; private set; }

        public CertificateAttributes(string subject, string issuer, string thumbprint)
        {
            Subject = subject;
            Issuer = issuer;                
            Thumbprint = thumbprint.Trim(
                new char[] { '\u200e', '\u200f' } // strip any lrt and rlt markers from copy/paste
                ); 
        }

        public bool IsMatch(X509Certificate cert)
        {
            bool subjectMatches = Subject.Replace(" ", "").Equals(cert.Subject.Replace(" ", ""), StringComparison.InvariantCulture);
            bool issuerMatches = Issuer.Replace(" ", "").Equals(cert.Issuer.Replace(" ", ""), StringComparison.InvariantCulture);
            bool thumbprintMatches = Thumbprint == String.Join(" ", cert.GetCertHash().Select(h => h.ToString("x2")));
            return subjectMatches && issuerMatches && thumbprintMatches; 
        }
    }

    private readonly List<CertificateAttributes> __knownSelfSignedCertificates = new List<CertificateAttributes> {
        new CertificateAttributes(  // can paste values from "view cert" dialog
            "CN = subject.company.int", 
            "CN = issuer.company.int", 
            "f6 23 16 3d 5a d8 e5 1e 13 58 85 0a 34 9f d6 d3 c8 23 a8 f4") 
    };       

    private static bool __createdSingleton = false;

    public SelfSignedCertificateValidator()
    {
        lock (this)
        {
            if (__createdSingleton)
                throw new Exception("Only a single instance can be instanciated.");

            // Hook in validation of SSL server certificates.  
            ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertficate;

            __createdSingleton = true;
        }
    }

    /// <summary>
    /// Validates the SSL server certificate.
    /// </summary>
    /// <param name="sender">An object that contains state information for this
    /// validation.</param>
    /// <param name="cert">The certificate used to authenticate the remote party.</param>
    /// <param name="chain">The chain of certificate authorities associated with the
    /// remote certificate.</param>
    /// <param name="sslPolicyErrors">One or more errors associated with the remote
    /// certificate.</param>
    /// <returns>Returns a boolean value that determines whether the specified
    /// certificate is accepted for authentication; true to accept or false to
    /// reject.</returns>
    private bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;   // Good certificate.

        Dbg.WriteLine("SSL certificate error: {0}", sslPolicyErrors);
        return __knownSelfSignedCertificates.Any(c => c.IsMatch(cert));            
    }
}

Una cosa a tener en cuenta es que el tener la ServicePointManager.ServerCertificateValidationCallback no parece significar que la comprobación de CRL y validación nombre del servidor no se hacen, sólo proporciona un medio para anular su resultado. Así que su servicio todavía podría tomar un tiempo para obtener una CRL, sólo se sabrá después de que fracasó algunas comprobaciones.

Yo estaba corriendo en el mismo problema que la OP en la solicitud Web tiraría esa excepción exacta. Había todo configurado correctamente pensé, se instaló el certificado, pude localizarlo en el almacén de equipo muy bien y adjuntarlo a la solicitud web, y que había desactivado la verificación de los certificados en el contexto de la petición.

Resultó que yo estaba corriendo bajo mi cuenta de usuario, y que el certificado se ha instalado en el almacén equipo. Esto hizo que la solicitud Web para lanzar esta excepción. Para resolver el problema que tenía que ser o bien se ejecuta como administrador o instalar el certificado al almacén de usuario y leerlo desde allí.

Parece que C # es capaz de encontrar el certificado en el almacén del equipo a pesar de que no se puede utilizar con una solicitud web, y que esto da lugar a excepción del PO siendo lanzada una vez que la solicitud web se emite.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top