Pergunta

Eu estou tentando se conectar a uma API que utiliza um certificado SSL auto-assinado. Eu estou fazendo isso usando da NET HttpWebRequest e HttpWebResponse objetos. E eu estou recebendo uma exceção que:

A conexão subjacente foi fechada:. Não foi possível estabelecer relação de confiança para o SSL / TLS canal seguro

Eu entendo o que isso significa. E eu entendo por .NET considera que deve me avisar e fechar a conexão. Mas, neste caso, eu gostaria de simplesmente se conectar à API de qualquer maneira, man-in-the-middle ser condenado.

Então, como faço para ir sobre a adição de uma exceção para este certificado auto-assinado? Ou é a abordagem para contar HttpWebRequest / resposta não para validar o certificado em tudo? Como eu faria isso?

Foi útil?

Solução

@Domster: que as obras, mas você pode querer impor um pouco de segurança, verificando se os jogos certificado de hash que você espera. Então, uma versão expandida parece um pouco com isso (baseado em algum código ao vivo estamos usando):

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;
}

Outras dicas

Acontece que, se você quiser apenas para validação de certificado desativar por completo, você pode mudar o ServerCertificateValidationCallback na ServicePointManager, assim:

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

Isto irá validar todos os certificados (incluindo inválido, expirou ou os auto-assinados).

Note que em .NET 4.5 você pode substituir SSL de validação por HttpWebRequest em si (e não via delegado global que afeta todos os pedidos):

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

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

Adicione o certificado auto-assinado para o computador local confiável de certificação de raiz Autoridades

Você pode importar o certificado executando o MMC como administrador.

Como exibir certificados com o snap-in MMC

O escopo do retorno de chamada de validação utilizado na de Domster resposta pode ser limitada a um pedido específico usando o parâmetro remetente no delegado ServerCertificateValidationCallback. A classe a seguir escopo simples usa essa técnica para temporariamente conectar um retorno de chamada de validação que executa somente para um determinado objeto de solicitação.

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;
    }
}

A classe acima pode ser usado para ignorar todos os erros de certificado para um pedido específico da seguinte forma:

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

Para adicionar como uma possível ajuda para alguém ... Se você quer que ele para solicitar ao usuário para instalar o certificado auto-assinado, você pode usar este código (modificado a partir de cima).

não requer direitos de administrador, instala para os usuários locais de confiança perfis:

    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;
    }

Isso parece funcionar bem para a nossa aplicação, e se o usuário não pressiona, a comunicação não vai funcionar.

Update: 2015/12/11 - Changed StoreName.Root para StoreName.My - Meu irá instalar na loja utilizadores locais, em vez de raiz. Root em alguns sistemas não funcionará, mesmo se você "Executar como administrador"

Apenas com base na resposta do devstuff para incluir assunto e emissor ... comentários aprovam ...

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));            
    }
}

Uma coisa a ter em mente é que ter o ServicePointManager.ServerCertificateValidationCallback não parece significar que a verificação CRL e validação servername não são feitas, só fornece um meio para substituir o seu resultado. Portanto, o seu serviço ainda pode demorar um pouco para obter uma CRL, você só vai saber depois que ele falhou algumas verificações.

Eu estava correndo para o mesmo problema que o OP quando o pedido web jogaria essa exceção exata. Eu tinha tudo configurado corretamente eu pensei, o certificado foi instalado, eu poderia localizá-lo no armazenamento do computador muito bem e anexá-lo ao pedido web, e eu tinha desativado a verificação de certificados no contexto de solicitação.

Acontece que eu estava correndo em minha conta de usuário, e que o certificado foi instalado para o armazenamento do computador. Isso fez com que a solicitação da Web para lançar essa exceção. Para resolver o problema que eu tinha que ser executado como administrador ou instalar o certificado para o armazenamento do usuário e lê-lo de lá.

Parece que C # é capaz de encontrar o certificado no armazenamento do computador, mesmo que não pode ser usado com um pedido web, e que isso resulta em exceção do OP sendo jogado uma vez que a solicitação da web é emitido.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top