Двухсторонняя аутентификация с помощью HTTPClient

Я пытаюсь сделать HTTP-запросы на сервер, для которого требуется двухстороннее SSL-соединение (аутентификация клиента). У меня есть файл .p12, содержащий несколько сертификатов и пароль. Запрос сериализуется с использованием буфера протоколов.

Моя первая мысль заключалась в том, чтобы добавить хранилище ключей к свойствам ClientCertificate WebRequestHandler, используемым HttpClient. Я также добавил хранилище ключей для своих доверенных корневых центров сертификации на своем компьютере.

Когда PostAsync выполняется, я всегда получаю "не могу создать безопасный канал ssl/tls". Очевидно, что-то, что я делаю неправильно, но я немного потерял здесь.

Любые указатели будут оценены.

    public void SendRequest()
    {
        try
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;

            var handler = new WebRequestHandler();

            // Certificate is located in bin/debug folder
            var certificate = new X509Certificate2Collection();
            certificate.Import("MY_KEYSTORE.p12", "PASSWORD", X509KeyStorageFlags.DefaultKeySet);

            handler.ClientCertificates.AddRange(certificate);
            handler.ServerCertificateValidationCallback = ValidateServerCertificate;

            var client = new HttpClient(handler)
            {
                BaseAddress = new Uri("SERVER_URL")
            };
            client.DefaultRequestHeaders.Add("Accept", "application/x-protobuf");
            client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/x-protobuf");
            client.Timeout = new TimeSpan(0, 5, 0);

            // Serialize protocol buffer payload
            byte[] protoRequest;
            using (var ms = new MemoryStream())
            {
                Serializer.Serialize(ms, MyPayloadObject());
                protoRequest = ms.ToArray();
            }

            var result = await client.PostAsync("/resource", new ByteArrayContent(protoRequest));

            if (!result.IsSuccessStatusCode)
            {
                var stringContent = result.Content.ReadAsStringAsync().Result;
                if (stringContent != null)
                {
                    Console.WriteLine("Request Content: " + stringContent);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            throw;
        }
   }

        private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            if (sslPolicyErrors == SslPolicyErrors.None)
                return true;

            Console.WriteLine("Certificate error: {0}", sslPolicyErrors);

            // Do not allow this client to communicate with unauthenticated servers.
            return false;
        }

ИЗМЕНИТЬ

Я даже не врывался в ValidateServerCertificate. Исключение вызывается, как только вызывается PostAsync. Протокол определенно TLS v1.

Клиентская ОС - Windows 8.1. Сервер закодирован в Java (не уверен, в какой ОС он работает. У меня нет доступа к нему. Это черный ящик.)

StackTrace

в System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext & context)  в System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar)

Нет внутреннего исключения.

Ответ 1

Вы пытались изменить security protocol на Ssl3? В любом случае вам нужно установить свойство Expect в значение true. Он исправит вашу ошибку. Далее вы можете изучить эту ссылку, чтобы получить больше знаний о передаче сертификата клиента для аутентификации.

public void SendRequest()
{
    try
    {
        ServicePointManager.Expect100Continue = true;
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;

        var handler = new WebRequestHandler();
        .....
    }
    ..
}

Ответ 2

Я пытался проверить протокол безопасности, используемый, когда я столкнулся с этим сообщением. Я обнаружил, что я делаю ошибку до ValidateServerCertificate, когда я использовал неверный протокол безопасности. (IIS 7.x по умолчанию используется SSL3.) Чтобы охватить все ваши базы для используемого протокола, вы можете определить все.   System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Ssl3;

Ответ 3

Я думаю, что это то, что вам нужно: Пример асинхронной реализации клиента/сервера SslStream

using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;


class Program
{
    static void Main(string[] args)
    {
        SecureTcpServer server = null;
        SecureTcpClient client = null;

        try
        {
            int port = 8889;

            RemoteCertificateValidationCallback certValidationCallback = null;
            certValidationCallback = new RemoteCertificateValidationCallback(IgnoreCertificateErrorsCallback);

            string certPath = System.Reflection.Assembly.GetEntryAssembly().Location;
            certPath = Path.GetDirectoryName(certPath);
            certPath = Path.Combine(certPath, "serverCert.cer");
            Console.WriteLine("Loading Server Cert From: " + certPath);
            X509Certificate serverCert = X509Certificate.CreateFromCertFile(certPath);

            server = new SecureTcpServer(port, serverCert,
                new SecureConnectionResultsCallback(OnServerConnectionAvailable));

            server.StartListening();

            client = new SecureTcpClient(new SecureConnectionResultsCallback(OnClientConnectionAvailable),
                certValidationCallback);

            client.StartConnecting("localhost", new IPEndPoint(IPAddress.Loopback, port));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);               
        }

        //sleep to avoid printing this text until after the callbacks have been invoked.
        Thread.Sleep(4000);
        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();

        if (server != null)
            server.Dispose();
        if (client != null)
            client.Dispose();

    }

    static void OnServerConnectionAvailable(object sender, SecureConnectionResults args)
    {
        if (args.AsyncException != null)
        {
            Console.WriteLine(args.AsyncException);
            return;
        }

        SslStream stream = args.SecureStream;

        Console.WriteLine("Server Connection secured: " + stream.IsAuthenticated);



        StreamWriter writer = new StreamWriter(stream);
        writer.AutoFlush = true;

        writer.WriteLine("Hello from server!");

        StreamReader reader = new StreamReader(stream);
        string line = reader.ReadLine();
        Console.WriteLine("Server Recieved: '{0}'", line == null ? "<NULL>" : line);

        writer.Close();
        reader.Close();
        stream.Close();
    }

    static void OnClientConnectionAvailable(object sender, SecureConnectionResults args)
    {
        if (args.AsyncException != null)
        {
            Console.WriteLine(args.AsyncException);
            return;
        }
        SslStream stream = args.SecureStream;

        Console.WriteLine("Client Connection secured: " + stream.IsAuthenticated);

        StreamWriter writer = new StreamWriter(stream);
        writer.AutoFlush = true;

        writer.WriteLine("Hello from client!");

        StreamReader reader = new StreamReader(stream);
        string line = reader.ReadLine();
        Console.WriteLine("Client Recieved: '{0}'", line == null ? "<NULL>" : line);

        writer.Close();
        reader.Close();
        stream.Close();
    }

    static bool IgnoreCertificateErrorsCallback(object sender,
        X509Certificate certificate,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors != SslPolicyErrors.None)
        {

            Console.WriteLine("IgnoreCertificateErrorsCallback: {0}", sslPolicyErrors);
            //you should implement different logic here...

            if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0)
            {
                foreach (X509ChainStatus chainStatus in chain.ChainStatus)
                {
                    Console.WriteLine("\t" + chainStatus.Status);
                }
            }
        }

        //returning true tells the SslStream object you don't care about any errors.
        return true;
    }
}

Ответ 4

Вы пробовали следующий код?

ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);

Пожалуйста, перед вашей строкой кода

handler.ClientCertificates.AddRange(certificate);

Ответ 5

Я использую ту же базу кода, что и вы, но вместо

certificate.Import("MY_KEYSTORE.p12", "PASSWORD", X509KeyStorageFlags.DefaultKeySet);

Я использую X509KeyStorageFlags.UserKeySet

Также я установил корневой сертификат в CurrentUser/CA и работает для меня.