#c# #asp.net-core #ssl #client-certificates
#c# #asp.net-ядро #ssl #клиент-сертификаты
Вопрос:
Когда третья сторона пытается вызвать мою конечную точку API с сертификатом в формате .cer, который я экспортировал из файла .pfx и отправил им. Они получат 403 — Запрещено: доступ запрещен. У вас нет разрешения на просмотр этого каталога или страницы с использованием предоставленных вами учетных данных.
Я исследую, что может быть причиной этой проблемы.
- Когда я устанавливаю / импортирую сертификат в формате .cer в хранилище сертификатов в разделе Personal, а затем пытаюсь вызвать свою конечную точку, я вижу, что моего сертификата НЕТ в списке сертификатов, и чем я нажимаю кнопку ok, я получаю 403 — Запрещено: доступ запрещен.
НО
- Когда я устанавливаю / импортирую сертификат в формате .pfx с помощью passepharse в хранилище сертификатов в разделе Personal, а затем пытаюсь вызвать свою конечную точку в браузере, а также с помощью postman НА ЭТОТ раз я вижу свой сертификат в списке сертификатов в браузере, а затем выбираю сертификат и нажимаю кнопку, я успешно попадаю в каталоги я также получаю ответ 200 ok в postman с, конечно, добавлением сертификата в формате .pfx в postman.
И я в замешательстве, теперь третья сторона принимает сертификат клиента только в формате .cer, и, как я понимаю, .pfx предназначен для внутренней организации, а не для внешней организации.
** Я должен отметить, что мой сертификат клиента не содержит закрытого ключа, он содержит только открытый ключ.
** Я уверен, что вся конфигурация на сервере и IIS верна.
** Я не уверен, как добавить закрытый ключ в мой клиентский сертификат в формате .cer! или я должен ?!
Я что-то пропустил здесь! прошло 4 дня, работая над этим, но все равно не повезло: (
Может кто-нибудь, пожалуйста, помогите мне или укажите мне правильное направление! спасибо 🙂
Вот как я получаю сертификат клиента в ASP.NET Ядро 3.1:
MyCertificateValidationService.cs
Я сравнил сертификат клиента, который у меня есть, с сертификатом клиента, который я получаю из запроса:
public class MyCertificateValidationService
{
public bool ValidateCertificate(X509Certificate2 clientCertificate)
{
try
{
var _path = @"c:tempClientCertification.cer";
var cert2 = new X509Certificate2(File.ReadAllBytes(_path));
if (clientCertificate.Thumbprint == cert2.Thumbprint)
{
return true;
}
}
catch (System.Exception)
{
throw;
}
return false;
}
Моя конечная точка API:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[Consumes("application/xml")]
[Produces("application/xml")]
[ProducesResponseType(typeof(DespatchAdvice), (int)HttpStatusCode.OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesDefaultResponseType]
[HttpPost("SendDespatch")]
public IActionResult SendDespatch([FromBody] DespatchAdvice despatches)
{
//do something
}
}
Startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<MyCertificateValidationService>();
services.AddSingleton<MailHandler>();
services.AddScoped<IDespatch, DespatchRepo>();
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options => // code from ASP.NET Core sample
{
// https://learn.microsoft.com/en-us/aspnet/core/security/authentication/certauth
options.AllowedCertificateTypes = CertificateTypes.All;
//options.RevocationMode = X509RevocationMode.NoCheck;
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService =
context.HttpContext.RequestServices.GetService<MyCertificateValidationService>();
if (validationService.ValidateCertificate(context.ClientCertificate))
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer),
new Claim(ClaimTypes.Name, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer)
};
context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
}
else
{
context.Fail("invalid cert");
}
return Task.CompletedTask;
}
};
});
services.AddAuthorization();
services.AddCertificateForwarding(options =>
{
options.CertificateHeader = "X-ARR-ClientCert";
options.HeaderConverter = (headerValue) =>
{
X509Certificate2 clientCertificate = null;
if (!string.IsNullOrWhiteSpace(headerValue))
{
byte[] bytes = StringToByteArray(headerValue);
clientCertificate = new X509Certificate2(bytes);
}
return clientCertificate;
};
});
services.AddControllers().AddXmlSerializerFormatters();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCertificateForwarding();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
private static byte[] StringToByteArray(string hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i = 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
Program.cs
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
=> WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureKestrel(options =>
{
var cert = new X509Certificate2(Path.Combine("cert.pfx"), "password");
options.ConfigureHttpsDefaults(o =>
{
o.ServerCertificate = cert;
o.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
});
})
.Build();
}
Комментарии:
1. Это сообщение об ошибке не связано с сертификатом, в нем говорится, что вы не можете перечислить содержимое определенной папки в IIS
2. Но как вы можете использовать сертификат без закрытого ключа для аутентификации клиента?
3. Должно быть наоборот — они должны предоставить вам открытый ключ (in .cer или иным образом), оставив закрытый ключ для себя.
4. @Evk итак, как я могу получить закрытый ключ ?! из-за того, что при экспорте .pfx я не могу выбрать да, экспортируйте закрытый ключ
5. Смотрите выше — вы не должны иметь дело с закрытыми ключами другой стороны, они называются закрытыми не просто так.
Ответ №1:
Давайте проясним, как работает проверка подлинности SSL-сертификата клиента. Ниже я предполагаю, что «сертификат» никогда не содержит закрытый ключ, только открытый ключ.
Клиент предоставляет сертификат, которым он владеет, во время подтверждения SSL. Сервер проверяет, соответствует ли этот сертификат некоторым произвольным критериям, например, сервер может потребовать, чтобы он был выдан определенным центром сертификации, или, как в вашем случае, что это конкретный сертификат (его отпечаток соответствует тому, что вы ожидаете).
Теперь клиент должен доказать серверу, что он действительно владеет закрытым ключом для этого сертификата, проще говоря, подписав некоторую информацию этим закрытым ключом, а затем сервер проверяет это с помощью сертификата клиента (отправленного ранее, как описано выше) с открытым ключом.
Если клиент успешно докажет, что он владеет закрытым ключом для данного сертификата, И этот сертификат соответствует критериям сервера — тогда клиент аутентифицируется и может продолжить.
Вы уже можете понять, почему ваш текущий подход не может работать — файл .cer, который вы отправляете клиенту, не содержит закрытого ключа, поэтому его нельзя использовать для целей аутентификации.
Теперь у вас есть два способа:
1 — ВЫ создаете новый сертификат и отправляете своему клиенту как сертификат, так и закрытый ключ. Вариант этого заключается в том, что вы создаете свой собственный центр сертификации, а затем выдаете такой сертификат клиента под этим органом. Затем в коде проверки вы можете просто убедиться, что сертификат был выдан вашим органом вместо прямого сравнения отпечатков пальцев. Это разумный способ, если у вас тысячи клиентов.
Недостатком такого подхода является то, что теперь у вас есть (или была когда-то) защищенная информация, которая вам не нужна — это закрытый ключ сертификата, выданный для вашего клиента. Если несанкционированный доступ произошел с использованием закрытого ключа этого клиента (у клиента был украден его закрытый ключ) — теоретически клиент может заявить, что ВЫ слили этот ключ. Другим недостатком является то, что вам приходится передавать конфиденциальные данные (закрытый ключ) по какому-либо, предпочтительно безопасному каналу. Если вы просто отправляете закрытый ключ клиенту по электронной почте — может случиться что-нибудь плохое (например, клиент не удалит его, а затем его электронная почта будет взломана, и ключ попадет к хакеру).
2 — ВАШ КЛИЕНТ генерирует сертификат и закрытый ключ. Это лучший способ, если у вас мало клиентов. Клиент сохраняет закрытый ключ для себя и отправляет вам сертификат (скажем, в формате .cer), который не содержит закрытого ключа. Теперь он проходит аутентификацию, как описано выше, и вы просто проверяете, что сертификат, предоставленный в SSL handshake, соответствует сертификату, отправленному вам клиентом заранее (как вы делаете сейчас, сравнивая отпечаток пальца). Asp.net затем убедитесь, что у клиента есть соответствующий закрытый ключ для этого сертификата.
Теперь никакие конфиденциальные данные никуда не нужно отправлять, и в случае утечки клиентом своего закрытого ключа — вы не можете нести за это ответственность, поскольку у вас никогда не было этого ключа в первую очередь.
Примечание: если вы идете по первому маршруту, генерируя сертификат для своего клиента, обратите внимание, что это новый сертификат, совершенно не связанный с вашим сертификатом сервера. Закрытый ключ сертификата вашего сервера, конечно, никогда не должен быть отправлен куда угодно. Это связано с вашим комментарием «но отправлять им pfx не опасно ?!». Нет, потому что вы только что сгенерировали этот pfx специально для этого клиента.
Комментарии:
1. спасибо за разъяснение того, как работает проверка подлинности SSL-сертификата клиента. Теперь я лучше понимаю 🙂 Я только что написал им о том, могут ли они отправить мне сертификат клиента, и я просто жду их ответа, и я дам вам знать, если есть что-нибудь 🙂