客户端证书-403-禁止访问:拒绝访问

时间:2020-11-09 11:00:44

标签: c# asp.net-core ssl client-certificates

当第三方尝试使用.cer格式的证书调用我的API端点时,该证书是从.pfx文件导出并发送给他们的。 他们将获得403-禁止访问:被拒绝。您无权使用您提供的凭据查看此目录或页面。

我调查了可能导致此问题的原因。

  • 当我在“个人”下的证书存储中以.cer格式安装/导入证书,然后尝试调用我的端点时,我可以看到我的证书不在证书列表中,然后我单击“确定”按钮,我将得到403-禁止访问:访问被拒绝。

但是

  • 当我使用Personal证书存储区中的passepharse安装/导入.pfx格式的证书,然后尝试在浏览器上以及邮递员此时调用我的端点时,我可以在浏览器的证书列表中看到我的证书,并且我选择证书,然后单击按钮,我将成功进入目录,并且在邮递员中也收到200 ok的响应,当然还要在邮递员中添加.pfx格式的证书。

现在我很困惑,第三方只接受.cer格式的客户证书,据我了解,.pfx是针对内部组织而不是外部组织的。

**我应注意,我的客户证书不包含私钥,而仅包含公钥。
**我确定服务器和IIS上的所有配置都是正确的。
**我不确定如何将私钥添加到.cer格式的客户证书中!还是我?!

我在这里错过了什么吗!已经花了4天的时间,但仍然没有运气:(

任何人都可以帮助我或为我指明正确的方向!谢谢:)

这是我如何在ASP.NET Core 3.1中获得客户端证书的方法:

MyCertificateValidationService.cs

我将自己拥有的客户端证书与从请求中获得的客户端证书进行了比较:

public class MyCertificateValidationService 
{
    public bool ValidateCertificate(X509Certificate2 clientCertificate)
    {
        try
        {
            var _path = @"c:\temp\ClientCertification.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://docs.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 个答案:

答案 0 :(得分:1)

让我们阐明SSL客户端证书身份验证的工作方式。下面我假设“证书” 从不包含私钥,仅包含公钥。

客户端在SSL握手期间提供了他拥有的证书。服务器验证该证书是否符合某些任意条件,例如服务器可能要求该证书是由某些证书颁发机构颁发的,或者像您一样,这是特定证书(它的指纹符合您的期望)。

现在,客户必须向服务器证明自己确实拥有此证书的私钥,以简单的方式通过用该私钥签名一些信息来进行证明,然后服务器用客户的证书(如上所述,之前发送)验证该公钥。

如果客户端成功证明自己拥有给定证书的私钥,并且该证书符合服务器标准,则客户端将通过身份验证并可以继续。

您已经知道为什么当前的方法无法正常工作-发送给客户端的.cer文件不包含私钥,因此不能用于身份验证。

现在您有两种方法:

1-您生成新证书,并将证书和私钥发送到您的客户端。这是当您创建自己的证书颁发机构,然后在该证书颁发机构下颁发此类客户证书时的变体。然后,在验证代码中,您只需确保证书是由您的授权机构签发的,而不是直接比较指纹。如果您有成千上万的客户,这是一种合理的方法。

这种方法的缺点是您现在拥有(或曾经拥有)不需要的安全信息-这是为您的客户颁发的证书的私钥。如果使用该客户的私钥发生了未经授权的访问(客户的私钥被盗),则客户理论上可以声称您泄露了此密钥。另一个缺点是,您必须通过一些安全通道(最好是安全通道)传递敏感数据(私钥)。如果您只是通过电子邮件将私钥发送给客户端-可能会发生任何不良情况(例如客户端不会删除私钥,那么以后他的电子邮件就会被黑客入侵,并且密钥会泄露给黑客)。

2-您的客户会生成证书和私钥。如果您没有太多客户,这是最好的方法。客户端为其自己存储私钥,并向您发送不包含私钥的证书(例如.cer格式)。现在,他如上所述进行身份验证,您只需验证SSL握手中提供的证书是否与客户端预先发送给您的证书相匹配(就像您现在通过比较指纹来进行操作)。然后,Asp.net确保客户端具有与此证书匹配的私钥。

现在,无需在任何地方发送敏感数据,以防客户端泄漏其私钥-您不必为此负责,因为您永远不会拥有此密钥。

侧面说明:如果您通过为客户端生成证书来进行第一条路线-请注意,它是新证书,与服务器证书完全无关。您的服务器证书私钥当然不应发送到任何地方。那与您的评论有关,“但是将pfx发送给他们并不危险吗?!”。不,因为您只是为该客户端生成了此pfx。