0

我正在尝试获得相互客户认证以在 Azure 中工作。我正在使用以下配置运行 Web 应用程序:

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();
        });
    }
}

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

2020-07-02 13:30:52.711 +00:00 [信息] Microsoft.AspNetCore.Hosting.Diagnostics:请求开始 HTTP/1.1 GET https://[REMOVED]/api/ping

2020-07-02 13:30:52.718 +00:00 [Trace] Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware:允许所有主机。

2020-07-02 13:30:53.107 +00:00 [警告] Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationHandler:证书验证失败,主题为 OU=Origin Pull,O="Cloudflare, Inc.",L= San Francisco, S=California, C=US.UntrustedRoot 已处理的证书链,但以不受信任的提供者信任的根证书终止。RevocationStatusUnknown 吊销功能无法检查证书的吊销。OfflineRevocation 吊销功能无法检查吊销,因为吊销服务器处于脱机状态。

2020-07-02 13:30:53.107 +00:00 [信息] Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationHandler:证书未通过身份验证。失败消息:客户端证书验证失败。

2020-07-02 13:30:53.110 +00:00 [调试] Microsoft.AspNetCore.Routing.Matching.DfaMatcher:为请求路径“/api/ping”找到 1 个候选者

看起来客户端证书不被接受。应该是吗?我的意思是,它是 Cloudflare。我在设置中做错了吗?我应该在我身边安装一些额外的东西吗?我在这里浏览了本指南:https: //support.cloudflare.com/hc/en-us/articles/204899617-Authenticated-Origin-Pulls并没有提及额外安装证书的任何内容。我应该在网络应用程序本身上安装 origin-pull-ca.pem 吗?

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

  • origin-pull-ca.pem:指纹:1F5BA8DCF83E6453DD75C47780906710901AD641(附加信息:CN=origin-pull.cloudflare.net, S=California, L=San Francisco, OU=Origin Pull, O="CloudFlare, Inc.", C=我们)
  • 从 Cloudflare 发送:指纹:A27996CBA564D24731BC76439C48920C1F7D4AA3(附加信息:OU=Origin Pull,O="Cloudflare, Inc.",L=San Francisco,S=California,C=US)

他们不应该是平等的吗?

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

4

2 回答 2

1

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

   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();
        });
    }
于 2020-07-09T09:06:28.430 回答
1

我在这里问的问题完全相同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;
            // https://stackoverflow.com/questions/6097671/how-to-verify-x509-cert-without-importing-root-cert (Azure)
            //chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;

            // https://stackoverflow.com/a/7332193
            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;
    }
}
于 2020-07-04T15:25:25.863 回答