【问题标题】:Thirdparty certificate authentication in .net core API between client and server API客户端和服务器 API 之间的 .net 核心 API 中的第三方证书身份验证
【发布时间】:2021-11-17 21:33:25
【问题描述】:

我正在尝试在 .net 核心 API(服务器/目标)中实现证书身份验证,并且此 API 将被调用到另一个 API(客户端)。这是向服务器/发出请求的客户端 API 的一段代码目标 api。但我在服务器/目标 api 上遇到错误。我正在从本地运行这两个服务,并且两个证书都已经安装 客户端控制器逻辑

[HttpGet]    
        public async Task<List<WeatherForecast>> Get()
        {
            
            List<WeatherForecast> weatherForecastList = new List<WeatherForecast>();
            X509Certificate2 clientCert = Authentication.GetClientCertificate();
            if (clientCert == null)
            {
                HttpActionContext actionContext = null;
                actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                {
                    ReasonPhrase = "Client Certificate Required"
                };
            }
            HttpClientHandler requestHandler = new HttpClientHandler();
            requestHandler.ClientCertificates.Add(clientCert);
            requestHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
            HttpClient client = new HttpClient(requestHandler)
            {
                BaseAddress = new Uri("https://localhost:11111/ServerAPI")
            };
            client.DefaultRequestHeaders
                      .Accept
                      .Add(new MediaTypeWithQualityHeaderValue("application/xml"));//ACCEPT head
            
            using (var httpClient = new HttpClient())
            {
                //httpClient.DefaultRequestHeaders.Accept.Clear();
                var request = new HttpRequestMessage()
                {
                    RequestUri = new Uri("https://localhost:44386/ServerAPI"),
                    Method = HttpMethod.Get,
                };
                request.Headers.Add("X-ARR-ClientCert", clientCert.GetRawCertDataString());
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT head
                //using (var response = await httpClient.GetAsync("https://localhost:11111/ServerAPI"))
                using (var response = await httpClient.SendAsync(request))
                {
                    if (response.StatusCode == System.Net.HttpStatusCode.OK)
                    {
                        string apiResposne = await response.Content.ReadAsStringAsync();
                        weatherForecastList = JsonConvert.DeserializeObject<List<WeatherForecast>>(apiResposne);
                    }
                }
            }
            return weatherForecastList;
        }

认证类

public static X509Certificate2 GetClientCertificate()
        {
            X509Store userCaStore = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
            try
            {
                string str_API_Cert_Thumbprint = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";                   

                userCaStore.Open(OpenFlags.ReadOnly);
                X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
                X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindByThumbprint, str_API_Cert_Thumbprint, false);

                X509Certificate2 clientCertificate = null;
                if (findResult.Count == 1)
                {
                    clientCertificate = findResult[0];
                    if(System.DateTime.Today >= System.Convert.ToDateTime(clientCertificate.GetExpirationDateString()))
                    {
                        throw new Exception("Certificate has already been expired.");
                    }
                    else if (System.Convert.ToDateTime(clientCertificate.GetExpirationDateString()).AddDays(-30) <= System.DateTime.Today)
                    {
                        throw new Exception("Certificate is about to expire in 30 days.");
                    }
                }
                else
                {
                    throw new Exception("Unable to locate the correct client certificate.");
                }
                return clientCertificate;
            }
            catch (Exception ex)
            {
                throw;
            }
            finally
            {
                userCaStore.Close();
            }
        }

服务器/目标 API 代码

[HttpGet]
    public IEnumerable<WeatherForecast> Getcertdata()
    {
        IHeaderDictionary headers = base.Request.Headers;
        X509Certificate2 clientCertificate = null;
        string certHeaderString = headers["X-ARR-ClientCert"];

        if (!string.IsNullOrEmpty(certHeaderString))
        { 
            //byte[] bytes = Encoding.ASCII.GetBytes(certHeaderString);
            //byte[] bytes = Convert.FromBase64String(certHeaderString);
            //clientCertificate = new X509Certificate2(bytes);              
            clientCertificate = new X509Certificate2(WebUtility.UrlDecode(certHeaderString));                
            var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
            if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
            {
                //Valida Cert
            }

        }
        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        }).ToArray();

        //return new List<WeatherForecast>();
    }

【问题讨论】:

    标签: asp.net-core-webapi x509certificate2 .net-core-authorization asp.net-core-authenticationhandler


    【解决方案1】:

    这里有更多问题,代码存在严重缺陷并且在各种方面不安全。让我们解释每个问题:

    • HttpClient 在客户端控制器逻辑中的 using 子句中

    尽管您希望将任何实现 IDisposable 的东西包装在 using 语句中。但是,HttpClient 的情况并非如此。连接不会立即关闭。对于客户端控制器操作的每个请求,都会建立到远程端点的新连接,而之前的连接处于TIME_WAIT 状态。在某些恒定负载下,您的 HttpClient 将耗尽 TCP 端口池(这是有限的),并且任何创建新连接的新尝试都会引发异常。以下是有关此问题的更多详细信息:You're using HttpClient wrong and it is destabilizing your software

    Microsoft 建议重新使用现有连接。一种方法是Use IHttpClientFactory to implement resilient HTTP requests。微软文章稍微谈到了这个问题:

    虽然这个类实现了 IDisposable,但声明和实例化 它在 using 语句中不是首选,因为当 HttpClient 对象被处理掉,底层的套接字不是 立即释放,这可能导致套接字耗尽问题。

    顺便说一句,您创建了一个client 变量,但不要以任何方式使用它。

    • 忽略证书验证问题

    行:

    requestHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
    

    使您容易受到 MITM 攻击。

    • 您的客户端证书身份验证错误

    行:

    request.Headers.Add("X-ARR-ClientCert", clientCert.GetRawCertDataString());
    

    如何进行客户端证书身份验证不是正确的方法。您实际上所做的是将证书的公共部分传递给服务器。就这样。您不证明拥有对您进行身份验证所需的私钥。正确的做法是:

    requestHandler.ClientCertificates.Add(clientCert);
    

    这将强制客户端和服务器执行正确的客户端身份验证并检查您是否拥有您通过的证书的私钥(它在 TLS 握手中自动完成)。如果您在服务器端有 ASP.NET,那么您可以这样阅读它(在控制器操作中):

    X509Certificate2 clientCert = Request.HttpContext.Connection.ClientCertificate
    if (clientCert == null) {
        return Unauthorized();
    }
    // perform client cert validation according server-side rules.
    
    • 非标准证书存储

    在身份验证类中,您打开StoreName.TrustedPeople 存储,而通常它应该是StoreName.MyTrustedPeople 并非旨在使用私钥存储证书。这不是功能问题,但这是不好的做法。

    • 身份验证类中不必要的try/catch 子句

    如果你故意在方法中抛出异常,不要使用try/catch。在您的情况下,您只需重新抛出异常,因此您正在做双重工作。还有这个:

    throw new Exception("Certificate is about to expire in 30 days.");
    

    在我身后。在技​​术上有效的证书上抛出异常?真的吗?

    • 服务器端代码

    如前所述,所有这些:

    IHeaderDictionary headers = base.Request.Headers;
    X509Certificate2 clientCertificate = null;
    string certHeaderString = headers["X-ARR-ClientCert"];
    if (!string.IsNullOrEmpty(certHeaderString))
        { 
            //byte[] bytes = Encoding.ASCII.GetBytes(certHeaderString);
            //byte[] bytes = Convert.FromBase64String(certHeaderString);
            //clientCertificate = new X509Certificate2(bytes);              
            clientCertificate = new X509Certificate2(WebUtility.UrlDecode(certHeaderString));                
            var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
            if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
            {
                //Valida Cert
            }
    
        }
    

    必须替换为:

    X509Certificate2 clientCert = Request.HttpContext.Connection.ClientCertificate
    if (clientCert == null) {
        return Unauthorized();
    }
    // perform client cert validation according server-side rules.
    

    顺便说一句:

    var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
    if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
    {
        //Valida Cert
    }
    

    这是您代码中的另一个灾难。您从 PFX 加载服务器证书只是为了比较他们的指纹?那么,您认为客户端将拥有服务器证书的副本?客户端和服务器证书不能相同。接下来是生成大量服务器证书的私钥文件的副本。您生成的私钥文件越多,过程越慢,您只会生成大量垃圾。您可以在我的博文中找到更多详细信息:Handling X509KeyStorageFlags in applications

    【讨论】:

      猜你喜欢
      • 2021-12-25
      • 1970-01-01
      • 2012-06-10
      • 2013-06-29
      • 1970-01-01
      • 1970-01-01
      • 2020-07-08
      • 2018-06-15
      • 1970-01-01
      相关资源
      最近更新 更多