#c# #.net #asp.net-core #.net-core #dotnet-httpclient
#c# #.net #asp.net-core #.net-core #dotnet-httpclient
Вопрос:
Я играл с .NET Core и создавал API, который использует платежные API. Существует сертификат клиента, который необходимо добавить к запросу на двустороннюю SSL-аутентификацию. Как я могу добиться этого с помощью .NET Core HttpClient
?
Я просмотрел различные статьи и обнаружил, что HttpClientHandler
в них нет возможности добавлять сертификаты клиентов.
Ответ №1:
Я выполнил новую установку для своей платформы (Linux Mint 17.3), выполнив следующие действия: Учебное пособие по .NET — Привет, мир за 5 минут. Я создал новое консольное приложение, ориентированное на netcoreapp1.0
платформу, смог отправить сертификат клиента; однако во время тестирования я получил сообщение «Ошибка SSL connect» ( CURLE_SSL_CONNECT_ERROR 35
), хотя я использовал действительный сертификат. Моя ошибка может быть специфичной для моего libcurl.
Я запустил то же самое в Windows 7, и это сработало именно так, как нужно.
// using System.Net.Http;
// using System.Security.Authentication;
// using System.Security.Cryptography.X509Certificates;
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls12;
handler.ClientCertificates.Add(new X509Certificate2("cert.crt"));
var client = new HttpClient(handler);
var result = client.GetAsync("https://apitest.startssl.com").GetAwaiter().GetResult();
Комментарии:
1. вау, это сработало, приятель, большое спасибо, что нашли время, чтобы помочь мне. Все эти дни я был в отпуске.
2. Мне любопытно. Вы заставили это работать на компьютере с Linux или Windows?
3. Я заставил его работать как на Windows (10), так и на Linux (Mint 17.3, 18 и 18.1). Я получил «ошибку SSL connect» (CURLE_SSL_CONNECT_ERROR 35), когда я попытался использовать сертификат без закрытого ключа.
4. @yfisaqt можете ли вы подробнее рассказать о том, как вы увидели ошибку CURL? Я обнаружил, что могу подписать сертификат в Windows, но тот же код в Ubuntu 17.04 вызывает ошибки.
5. @Tom Чтобы быть техническим, это зависит от формата файла. Имейте в виду, что на этот вопрос был дан ответ до того, как я обновился до .NET Core SDK 2, поэтому в версии 2.0 это может вести себя немного по-другому. В то время, когда я отвечал на это, Windows требовала, чтобы файл был PFX, но в Linux я смог использовать обычный сертификат (без закрытого ключа).
Ответ №2:
У меня есть аналогичный проект, в котором я взаимодействую между службами, а также между мобильными и настольными компьютерами с помощью службы.
Мы используем сертификат Authenticode из EXE-файла, чтобы убедиться, что запросы выполняются нашими двоичными файлами.
На стороне запроса (упрощено для post).
Module m = Assembly.GetEntryAssembly().GetModules()[0];
using (var cert = m.GetSignerCertificate())
using (var cert2 = new X509Certificate2(cert))
{
var _clientHandler = new HttpClientHandler();
_clientHandler.ClientCertificates.Add(cert2);
_clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
var myModel = new Dictionary<string, string>
{
{ "property1","value" },
{ "property2","value" },
};
using (var content = new FormUrlEncodedContent(myModel))
using (var _client = new HttpClient(_clientHandler))
using (HttpResponseMessage response = _client.PostAsync($"{url}/{controler}/{action}", content).Result)
{
response.EnsureSuccessStatusCode();
string jsonString = response.Content.ReadAsStringAsync().Resu<
var myClass = JsonConvert.DeserializeObject<MyClass>(jsonString);
}
}
Затем я использую следующий код для действия, которое получает запрос:
X509Certificate2 clientCertInRequest = Request.HttpContext.Connection.ClientCertificate;
if (!clientCertInRequest.Verify() || !AllowedCerialNumbers(clientCertInRequest.SerialNumber))
{
Response.StatusCode = 404;
return null;
}
Мы скорее предоставляем 404, чем 500, поскольку нам нравятся те, которые пытаются получить неверный запрос по URL, а затем сообщают им, что они «на правильном пути»
В .NET Core способ получения сертификата больше не заключается в переходе по модулю. Современный способ, который может сработать для вас, это:
private static X509Certificate2? Signer()
{
using var cert = X509Certificate2.CreateFromSignedFile(Assembly.GetExecutingAssembly().Location);
if (cert is null)
return null;
return new X509Certificate2(cert);
}
Комментарии:
1. Как вы подтверждаете, что запрос клиента выполнен с помощью HTTP / 1.1? В HTTP / 2 нет аутентификации по сертификату клиента.
2. Я знаю, что это устарело, но чтобы ответить на ваш комментарий:
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
Ответ №3:
Я не использую .NET для моего клиента, но на стороне сервера его можно настроить просто через IIS, развернув мой ASP.NET Основной веб-сайт IIS, настройка IIS для HTTPS клиентских сертификатов:
Настройка сертификата клиента IIS:
Затем вы можете получить его просто в коде:
var clientCertificate = await HttpContext.Connection.GetClientCertificateAsync();
if(clientCertificate!=null)
return new ContentResult() { Content = clientCertificate.Subject };
У меня все работает нормально, но я использую curl или chrome в качестве клиентов, а не .ЧИСТЫЕ. Во время квитирования HTTPS клиент получает запрос от сервера на предоставление сертификата и отправляет его на сервер.
Если вы используете клиент .NET Core, у него не может быть кода, зависящего от платформы, и было бы разумно, если бы он не мог подключиться к какому-либо хранилищу сертификатов, специфичному для ОС, чтобы извлечь его и отправить на сервер. Если вы компилировали с .NET 4.5.x, то это кажется простым:
Использование HttpClient с аутентификацией на стороне клиента на основе SSL / TLS
Это похоже на компиляцию curl. Если вы хотите иметь возможность подключать его к хранилищу сертификатов Windows, вы должны скомпилировать его для какой-либо конкретной библиотеки Windows.
Комментарии:
1. спасибо, что уделили время ответу, но я использовал это для asp.net ядро. Приведенный выше код сработал для меня.
Ответ №4:
После долгих испытаний с этой проблемой я закончил с этим.
- Используя
SSL
, я создалpfx
файл из сертификата и ключа. - Создайте
HttpClient
следующий:
_httpClient = new(new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
SslProtocols = SslProtocols.Tls12,
ClientCertificates = { new X509Certificate2(@"C:kambiDev.pfx") }
});
Комментарии:
1. Он не работает на компьютере с Linux.
Ответ №5:
Может использоваться как для .NET Core 2.0< и .NET Framework 4.7.1<:
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(new X509Certificate2("cert.crt"));
var client = new HttpClient(handler);
Комментарии:
1. обработчик. ClientCertificates. Add(new X509Certificate2(«cert.crt»)) работает только с Framework 4.8 ( learn.microsoft.com/ru-ru/dotnet/api /… )
2. @Kate Пропустил от меня, что это не сработало с
.Net Framework 4.5
< но код работает с..Net Framework 4.7.1
Протестировал его локально, и в документации тоже так сказано. Обновлен ответ.3. Спецификация версии, похоже, перевернута, я думаю, она должна гласить: Может использоваться как для .NET Core> 2.0, так и для .NET Framework> 4.7.1: обратите внимание на инверсию знака (ов).
Ответ №6:
Сделайте всю конфигурацию в Main() следующим образом:
public static void Main(string[] args)
{
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
string env="", sbj="", crtf = "";
try
{
var whb = WebHost.CreateDefaultBuilder(args).UseContentRoot(Directory.GetCurrentDirectory());
var environment = env = whb.GetSetting("environment");
var subjectName = sbj = CertificateHelper.GetCertificateSubjectNameBasedOnEnvironment(environment);
var certificate = CertificateHelper.GetServiceCertificate(subjectName);
crtf = certificate != null ? certificate.Subject : "It will after the certification";
if (certificate == null) // present apies even without server certificate but dont give permission on authorization
{
var host = whb
.ConfigureKestrel(_ => { })
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseConfiguration(configuration)
.UseSerilog((context, config) =>
{
config.ReadFrom.Configuration(context.Configuration);
})
.Build();
host.Run();
}
else
{
var host = whb
.ConfigureKestrel(options =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 443), listenOptions =>
{
var httpsConnectionAdapterOptions = new HttpsConnectionAdapterOptions()
{
ClientCertificateMode = ClientCertificateMode.AllowCertificate,
SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
ServerCertificate = certificate
};
listenOptions.UseHttps(httpsConnectionAdapterOptions);
});
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls("https://*:443")
.UseStartup<Startup>()
.UseConfiguration(configuration)
.UseSerilog((context, config) =>
{
config.ReadFrom.Configuration(context.Configuration);
})
.Build();
host.Run();
}
Log.Logger.Information("Information: Environment = " env
" Subject = " sbj
" Certificate Subject = " crtf);
}
catch(Exception ex)
{
Log.Logger.Error("Main handled an exception: Environment = " env
" Subject = " sbj
" Certificate Subject = " crtf
" Exception Detail = " ex.Message);
}
}
Настройте файл startup.cs следующим образом:
#region 2way SSL settings
services.AddMvc();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CertificateAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CertificateAuthenticationDefaults.AuthenticationScheme;
})
.AddCertificateAuthentication(certOptions =>
{
var certificateAndRoles = new List<CertficateAuthenticationOptions.CertificateAndRoles>();
Configuration.GetSection("AuthorizedCertficatesAndRoles:CertificateAndRoles").Bind(certificateAndRoles);
certOptions.CertificatesAndRoles = certificateAndRoles.ToArray();
});
services.AddAuthorization(options =>
{
options.AddPolicy("CanAccessAdminMethods", policy => policy.RequireRole("Admin"));
options.AddPolicy("CanAccessUserMethods", policy => policy.RequireRole("User"));
});
#endregion
Помощник по сертификату
public class CertificateHelper
{
protected internal static X509Certificate2 GetServiceCertificate(string subjectName)
{
using (var certStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(
X509FindType.FindBySubjectDistinguishedName, subjectName, true);
X509Certificate2 certificate = null;
if (certCollection.Count > 0)
{
certificate = certCollection[0];
}
return certificate;
}
}
protected internal static string GetCertificateSubjectNameBasedOnEnvironment(string environment)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile($"appsettings.{environment}.json", optional: false);
var configuration = builder.Build();
return configuration["ServerCertificateSubject"];
}
}
Ответ №7:
Если вы посмотрите на стандартную ссылку .NET для класса HttpClientHandler, вы увидите, что свойство ClientCertificates существует, но скрыто из-за использования EditorBrowsableState .Никогда. Это не позволяет IntelliSense отображать его, но все равно будет работать в коде, который его использует.
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public System.Security.Cryptography.X509Certificates.X509CertificateCollection ClientCertificates { get; }
Ответ №8:
Я подумал, что лучший ответ на этот вопрос был предоставлен здесь.
Используя заголовок X-ARR-ClientCert, вы можете предоставить информацию о сертификате.
Адаптированное решение здесь:
X509Certificate2 certificate;
var handler = new HttpClientHandler {
ClientCertificateOptions = ClientCertificateOption.Manual,
SslProtocols = SslProtocols.Tls12
};
handler.ClientCertificates.Add(certificate);
handler.CheckCertificateRevocationList = false;
// this is required to get around self-signed certs
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) => {
return true;
};
var client = new HttpClient(handler);
requestMessage.Headers.Add("X-ARR-ClientCert", certificate.GetRawCertDataString());
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "application/json");
var response = await client.SendAsync(requestMessage);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var keyResponse = JsonConvert.DeserializeObject<KeyResponse>(responseContent);
return keyResponse;
}
И в процедуре запуска вашего сервера .net core:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddCertificateForwarding(options => {
options.CertificateHeader = "X-ARR-ClientCert";
options.HeaderConverter = (headerValue) => {
X509Certificate2 clientCertificate = null;
try
{
if (!string.IsNullOrWhiteSpace(headerValue))
{
var bytes = ConvertHexToBytes(headerValue);
clientCertificate = new X509Certificate2(bytes);
}
}
catch (Exception)
{
// invalid certificate
}
return clientCertificate;
};
});
}
Комментарии:
1. X-ARR-ClientCert — это специфичная для Azure вещь, и этот код фактически не подтверждает, что у клиента есть закрытый ключ для сертификата. Он просто прикрепляет открытый ключ к запросу. Обратите внимание, что связанная статья была исправлена, чтобы действительно правильно использовать сертификат клиента.