OnCertificateValidated 不是 运行 - 自签名证书客户端身份验证 - ASP.NET Core 和 Kestrel

OnCertificateValidated not running - Self-Signed Certificate Client Authentication - ASP.NET Core and Kestrel

我想在 Kestrel 上使用基于证书的身份验证来验证连接到我的 ASP.NET Core Web API (.NET 5) 运行 的客户端。

在我的 Startup.cs 我的 ConfigureServices 中有以下内容:

services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(options =>
    {
        options.AllowedCertificateTypes = CertificateTypes.All;
        options.Events = new CertificateAuthenticationEvents
        {
            OnCertificateValidated = context =>
            {
                // More code to verify certificates
            },
            OnAuthenticationFailed = context =>
            {
                // More code
            }
        };
    });

// Other services

Configure中:

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthentication();

app.UseEndpoints(endpoints =>
{
    // Endpoints
});

Program.cs 中,我包括:

webBuilder.ConfigureKestrel(o =>
{
    o.ConfigureHttpsDefaults(o =>
        o.ClientCertificateMode = ClientCertificateMode.RequireCertificate);
});

如果我在浏览器中连接到 API,它会提示我输入证书,但是在我 select 证书之后,OnCertificateValidatedOnAuthenticationFailed 事件被触发。经过进一步测试,我意识到 Startup.csAddCertificate 调用中的整个选项配置委托从未运行。这让我觉得我缺少 Kestrel 的某种配置,但我不知道那是什么。请注意,我的网站 API 不使用 IIS 托管。我还需要做什么才能使用基于证书的自签名身份验证?

我目前的代码基于此处文档中的说明:https://docs.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-5.0

好的,所以最后我能够解决我自己的问题。解决它有两个不同的部分,但最终只需要对我的项目代码进行一些小的修改。

正在识别客户端证书

首先,服务器未将自签名客户端证书识别为有效证书。这可以通过 1. 将所有客户端证书(或对它们全部签名的根 CA)添加到操作系统的受信任证书库或 2. 向 kestrel 添加 ClientCertificateValidation 回调以确定是否或没有证书被接受或拒绝。

#2 的示例(对 Program.cs 中的 ConfigureHttpsDefaults lambda 的调整)如下:

webBuilder.ConfigureKestrel(o =>
{
    o.ConfigureHttpsDefaults(opts =>
    {
        opts.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
        opts.ClientCertificateValidation = (cert, chain, policyErrors) =>
        {
            // Certificate validation logic here
            // Return true if the certificate is valid or false if it is invalid
        };
    });
});

作为旁注,调用 opts.AllowAnyClientCertificate() 是一个 shorthand,用于添加一个 ClientCertificateValidation 回调,该回调始终 returns 为真,使所有自签名证书都有效。


需要授权

应用这些方法中的任何一种后,我的 API 将接受来自有效证书的查询,但我在 OnCertificateValidated 事件中的额外证书验证逻辑仍然没有 运行ning。这是因为,根据 ASP.NET Core issue #14033, this event's extra certificate validation will never run unless authorization is enabled for the endpoint being accessed. This makes sense because this event is often used to generate a ClaimsPrincipal from a certificate per the ASP.NET Core Docs on the subject 的评论。将 ASP.NET 设置为使用授权并要求对 API 调用进行授权(例如,通过将 [Authorize] 属性应用于所有控制器)会导致对 运行 的额外身份验证检查 API 来电。

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthentication();

// Adding this and adding the [Authorize] attribute
// to controllers fixes the problem.
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    // Endpoints
});

在此之后,我的 OnCertificateValidated 事件为每个连接调用,我能够执行额外的身份验证逻辑并拒绝无效证书。

接受的答案对我很有帮助(谢谢!)但它并没有完全解决我的问题。

我发现 ClientCertificateValidation 函数不是唯一验证(和拒绝)证书的地方。在处理 ClientCertificateValidation 之后,使用 AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme). AddCertificate... 还会触发另一个验证。

为了解决我的问题,我必须在 CertificateAuthenticationOptions:

的行中配置一个 CustomTrustStore
AddCertificate(options =>
{
    options.AllowedCertificateTypes = CertificateTypes.All;
    options.ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust;
    options.CustomTrustStore = new X509Certificate2Collection { rootCert };
    options.RevocationMode = X509RevocationMode.NoCheck;
    options.Events = new CertificateAuthenticationEvents
    {
        OnCertificateValidated = context =>
        {
            if (validationService.ValidateCertificate(context.ClientCertificate))
            {
                context.Success();
            }
            else
            {
                context.Fail("invalid cert");
            }

            return Task.CompletedTask;
        },
        OnAuthenticationFailed = context =>
        {
            context.Fail("invalid cert");
            return Task.CompletedTask;
        }
    };
});

其中 rootCert 是通过以下方式启动的:

var rootCert = new X509Certificate2("RootCert.pfx", "1234");

并且 RootCert.pfx 作为文件添加到项目中。

由于我们现在使用 AddAuthentication(.).AddCertificate 的内置验证,我们应该使用 AllowAnyCertificate() 禁用较早的验证(此验证不知道我们的自定义根信任):

builder.WebHost.ConfigureKestrel(kso =>
{
    kso.ConfigureHttpsDefaults(cao => {
        cao.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
        cao.AllowAnyClientCertificate();
        cao.CheckCertificateRevocation = false;
    });
});