Двухсторонняя аутентификация с помощью 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 и работает для меня.