证书验证失败:验证证书时客户端证书验证失败

Certificate validation failed: validation of client side certificate fails when the certificate is validated

我正在尝试获得在 Azure 中工作的相互客户端认证。我是 运行 具有此配置的网络应用程序:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
        .AddCertificate();

        services.AddCertificateForwarding(options =>
            options.CertificateHeader = "X-ARR-ClientCert");

        services.AddHttpClient();
        services.AddControllers();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseAuthentication();
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseCertificateForwarding();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

为此,我添加了扩展,以便网络应用程序将客户端证书发送到我的应用程序。当我部署它时,它很好。我前面有 cloudflare 并启用了 Origin Pull,我可以验证客户端证书是否已发送。我可以看到,当我尝试直接在 azurewebsites.net 域上访问 Web 应用程序时,我的浏览器正在请求证书。如果我尝试通过 Cloudflare,它会显示该网页。我以为这行得通,但是当我检查日志时,我得到了这个:

2020-07-02 13:30:52.711 +00:00 [Information] Microsoft.AspNetCore.Hosting.Diagnostics: Request starting HTTP/1.1 GET https://[REMOVED]/api/ping

2020-07-02 13:30:52.718 +00:00 [Trace] Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware: All hosts are allowed.

2020-07-02 13:30:53.107 +00:00 [Warning] Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationHandler: Certificate validation failed, the subject was OU=Origin Pull, O="Cloudflare, Inc.", L=San Francisco, S=California, C=US.UntrustedRoot A certificate chain processed but terminated in a root certificate which is not trusted by the trusted provider. RevocationStatusUnknown The revocation function was unable to check revocation for the certificate.OfflineRevocation The revocation function was unable to check revocation because the revocation server was offline.

2020-07-02 13:30:53.107 +00:00 [Information] Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationHandler: Certificate was not authenticated. Failure message: Client certificate failed validation.

2020-07-02 13:30:53.110 +00:00 [Debug] Microsoft.AspNetCore.Routing.Matching.DfaMatcher: 1 candidate(s) found for the request path '/api/ping'

客户端证书似乎未被接受。应该吗?我的意思是,它是 Cloudflare。我在我的设置中做错了什么吗?我应该在我这边安装一些额外的东西吗?我在这里查看了本指南:https://support.cloudflare.com/hc/en-us/articles/204899617-Authenticated-Origin-Pulls,它没有提到任何关于额外安装证书的内容。我是否应该在 Web 应用程序本身上安装 origin-pull-ca.pem?

当我将发给我的证书与origin-pull-ca.pem进行比较时,两者不相等:

他们不应该相等吗?

请注意:我不是证书、SSL 等方面的专家。我正在尝试在这里学习 :)

我在这里问的问题完全相同 https://community.cloudflare.com/t/manual-authenticated-origin-pulls-verification/145614。不知道为什么,但 A27996CBA564D24731BC76439C48920C1F7D4AA3 是正确的。

编辑:更新链

public class CloudflareClientCertificateMiddleware
{
    private static X509Certificate2 _cloudflareOriginPullCert;
    private readonly RequestDelegate _next;

    public CloudflareClientCertificateMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        if (_cloudflareOriginPullCert == null)
            _cloudflareOriginPullCert = Helpers.CertificateHelper.GetCertificateInSpecifiedStore("origin-pull.cloudflare.net", StoreName.Root, StoreLocation.LocalMachine);

        bool isCloudflareCertificate = true;
        X509Certificate2 clientCertificate = context.Connection.ClientCertificate;

        using (X509Chain chain = new X509Chain())
        {
            chain.ChainPolicy.ExtraStore.Add(_cloudflareOriginPullCert);
            chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
            //  (Azure)
            //chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;

            // 
            if (clientCertificate == null || chain.Build(clientCertificate) == false)
                isCloudflareCertificate = false;
            // Make sure we have the same number of elements.
            if (isCloudflareCertificate && chain.ChainElements.Count != chain.ChainPolicy.ExtraStore.Count + 1)
                isCloudflareCertificate = false;

            // Make sure all the thumbprints of the CAs match up.
            // The first one should be 'primaryCert', leading up to the root CA.
            if (isCloudflareCertificate)
            {
                for (int i = 1; i < chain.ChainElements.Count; i++)
                {
                    if (chain.ChainElements[i].Certificate.Thumbprint != chain.ChainPolicy.ExtraStore[i - 1].Thumbprint)
                        isCloudflareCertificate = false;
                }
            }
        }

        if (isCloudflareCertificate)
            await _next.Invoke(context);
        else
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
    }
}

基本上我可以这样做,来验证链

   private bool VerifyCertificate(X509Certificate2 client, ILogger<Startup> logger)
    {
        X509Chain chain = new X509Chain();

        var authority = GetInstalledCert();

        chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
        chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;

        if (!chain.Build(client))
            return false;

        var valid = chain.ChainElements
            .Cast<X509ChainElement>()
            .Any(x => x.Certificate.Thumbprint == authority.Thumbprint);

        if (!valid)
            return false;

        return true;
    }

    private X509Certificate2 GetInstalledCert()
    {
        X509Certificate2 cert = null;
        X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        certStore.Open(OpenFlags.ReadOnly);
        X509Certificate2Collection certCollection = certStore.Certificates.Find(
                                   X509FindType.FindByThumbprint,
                                   "1F5BA8DCF83E6453DD75C47780906710901AD641",
                                   false);

        if (certCollection.Count > 0)
        {
            cert = certCollection[0];
        }

        certStore.Close();

        return cert;
    }

    private X509Certificate2 GetClientCert(IHeaderDictionary headers)
    {
        var certHeader = headers["X-ARR-ClientCert"];

        if (certHeader.Any())
        {
            byte[] clientCertBytes = Convert.FromBase64String(certHeader);
            var certificate = new X509Certificate2(clientCertBytes);

            return certificate;
        }

        return null;
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.Use(async (context, next) =>
        {
            var clientCert = GetClientCert(context.Request.Headers);

            bool verify = VerifyCertificate(clientCert, logger);

            if(verify)
            {
                await next.Invoke();
            }
            else
            {
                context.Response.StatusCode = 404;
            }
        });


        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }