【问题标题】:ASP.NET CORE 5.0 TLS 1.2 issue on linux but on asp.net core 3.1 are workingLinux 上的 ASP.NET CORE 5.0 TLS 1.2 问题,但在 asp.net core 3.1 上运行
【发布时间】:2021-07-05 23:48:16
【问题描述】:

我在 asp.net core 5.0 和 linux 中遇到了 TLS 1.2 的问题。 它只发生在 asp.net core 5.0 中,同样的代码在 asp.net core 3.1 上运行

使用 HttpClient 和 net5.0 在 Ubuntu 18.04/20.04 上使用 OpenSSL 时 SSL 握手失败

System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
       ---> System.IO.IOException:  Received an unexpected EOF or 0 bytes from the transport stream.
         at System.Net.Security.SslStream.<FillHandshakeBufferAsync>g__InternalFillHandshakeBufferAsync|182_0[TIOAdapter](TIOAdapter adap, ValueTask`1 task, Int32 minSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
         --- End of inner exception stack trace ---
         at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.GetHttp2ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
         at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.DecompressionHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
         at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
         at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)

我在下面分享一个控制台应用程序,您应该查看它以了解此问题。 不幸的是,我无法共享应用程序的凭据,并且出于 nda 的原因,使用其 api 的提供者也无法共享。我们还将我们的 ips 列入防火墙白名单。 但我分享了代码,这样你就可以看到我尝试过的所有内容

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Test
{
    class Program
    {
        static async Task<int> Main(string[] args)
        {
            var builder = new HostBuilder()
                 .ConfigureServices((hostContext, services) =>
                 {
                     services.AddHttpClient("configured-certificate")
                      .ConfigurePrimaryHttpMessageHandler(() =>
                      {
                          return new HttpClientHandler()
                          {
                              CheckCertificateRevocationList = false,
                              UseDefaultCredentials = false,
                              ClientCertificateOptions = ClientCertificateOption.Manual,
                              SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11,
                              ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => ServerCertificateCustomValidation(message, cert, chain, errors),
                              AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
                              //ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
                              //ServerCertificateCustomValidationCallback = delegate { return true; },
                              CookieContainer = new CookieContainer()
                          };
                      });

                     services.AddTransient<ITestService, TestService>();
                     services.AddLogging((configure) =>
                     {
                         configure.AddConsole();
                         configure.AddDebug();
                     });
                     var env = hostContext.HostingEnvironment;
                     Console.Write($"Environtment: {env.EnvironmentName}\n");
                     var configurationBuilder = new ConfigurationBuilder()
                        .SetBasePath(env.ContentRootPath)
                        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                        .AddEnvironmentVariables();
                        
                     hostContext.Configuration = configurationBuilder.Build();
                 })
                 .UseConsoleLifetime();

            var host = builder.Build();

            ServicePointManager.SecurityProtocol =   SecurityProtocolType.Tls12;
            ServicePointManager.ServerCertificateValidationCallback +=
                 (sender, certificate, chain, errors) =>
                 {
                     return true;
                 };

            try
            {
                var myService = host.Services.GetRequiredService<ITestService>();
                var pageContent = await myService.GetPage();

                Console.WriteLine(pageContent);
            }
            catch (Exception ex)
            {
                var logger = host.Services.GetRequiredService<ILogger<Program>>();

                logger.LogError(ex, "An error occurred.");
            }

            return 0;
        }

        private static bool ServerCertificateCustomValidation(HttpRequestMessage requestMessage, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors sslErrors)
        {
            //It is possible inpect the certificate provided by server
            Console.WriteLine($"Requested URI: {requestMessage.RequestUri}");
            Console.WriteLine($"Effective date: {certificate.GetEffectiveDateString()}");
            Console.WriteLine($"Exp date: {certificate.GetExpirationDateString()}");
            Console.WriteLine($"Issuer: {certificate.Issuer}");
            Console.WriteLine($"Subject: {certificate.Subject}");

            //Based on the custom logic it is possible to decide whether the client considers certificate valid or not
            Console.WriteLine($"Errors: {sslErrors}");

            return true;
            //return sslErrors == SslPolicyErrors.None;
        }
    }

    public interface ITestService
    {
        Task<string> GetPage();
    }

    public class TestService : ITestService
    {
        private readonly IHttpClientFactory _clientFactory;

        public TestService(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task<string> GetPage()
        {
            var uri = "https://secureuri";

            var client = _clientFactory.CreateClient("configured-certificate");
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", "fglsdhjgoñisdjfhgoishdfg=");
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
            client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
            client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate"));
            client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("br"));

            var xml = System.IO.File.ReadAllText("request.xml");
            var data = new StringContent(xml, Encoding.UTF8, "application/xml");

            var response = await client.PostAsync(uri, data);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                return $"StatusCode: {response.StatusCode}";
            }
        }
    }
}

发布配置文件如下:

<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121. 
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration>Release</Configuration>
    <Platform>Any CPU</Platform>
    <PublishDir>D:\Test</PublishDir>
    <PublishProtocol>FileSystem</PublishProtocol>
    <TargetFramework>net5.0</TargetFramework>
    <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
    <SelfContained>false</SelfContained>
    <PublishSingleFile>False</PublishSingleFile>
  </PropertyGroup>
</Project>

我在 asp.net core 3 上有结果,并且 curl 在同一台服务器上

root@skynet:# curl -u user:passwd -kv https://xmldirect.ehi.com/services30/OTA30SOAP
*   Trying 12.43.130.115:443...
* TCP_NODELAY set
* Connected to xmldirect.ehi.com (12.43.130.115) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=US; postalCode=63105; ST=Missouri; L=St. Louis; street=600 Corporate Park Dr.; O=Enterprise Holdings Inc.; CN=xmldirect.ehi.com
*  start date: Mar 12 00:00:00 2020 GMT
*  expire date: Mar 12 23:59:59 2022 GMT
*  issuer: C=GB; ST=Greater Manchester; L=Salford; O=COMODO CA Limited; CN=COMODO RSA Organization Validation Secure Server CA
*  SSL certificate verify ok.
* Server auth using Basic with user 'user'
> GET /services30/OTA30SOAP HTTP/1.1
> Host: xmldirect.ehi.com
> Authorization: Basic fglsdhjgoñisdjfhgoishdfg=
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 05 Jul 2021 23:58:48 GMT
< Server: Apache
< Content-Length: 0
< Content-Type: application/xml
< Vary: Accept-Encoding

实时环境

Distributor ID: Ubuntu
Description: Ubuntu 18.04.5 LTS
Release: 18.04
Codename: bionic

OpenSSL> version
OpenSSL 1.0.2s 28 May 2019

测试环境

Distributor ID: Ubuntu
Description: Ubuntu 20.04.2 LTS
Release: 20.04
Codename: focal

OpenSSL> version
OpenSSL 1.1.1f 31 Mar 2020

客户端 Hello 上的 Asp.net core 3.1 密码(28 个套件)

Asp.net core 5.0 Cipher on Client Hello(9 个套件)

https://github.com/dotnet/runtime/issues/55227

在好的情况下,客户端提供一堆密码,服务器将选择密码套件:TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d) (packet 6 -> ServerHello) 在 5.0 中,默认密码仅限于目前被认为是强大且安全的密码。上面那个不在列表中

【问题讨论】:

标签: c# asp.net .net-core .net-5 asp.net5


【解决方案1】:

我基本上碰到https://docs.microsoft.com/en-us/dotnet/core/compatibility/cryptography/5.0/default-cipher-suites-for-tls-on-linux

我在这篇帖子 https://askubuntu.com/questions/1233186/ubuntu-20-04-how-to-set-lower-ssl-security-levelhttps://github.com/dotnet/runtime/issues/45244 之后解决了这个问题

我在 /etc/ssl/openssl.cnf 中修改了以下内容

  1. 在oid部分下面添加系统默认部分
# Extra OBJECT IDENTIFIER info:
#oid_file               = $ENV::HOME/.oid
oid_section             = new_oids

# System default
openssl_conf = default_conf
  1. 在文件末尾添加默认部分
[default_conf]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT:@SECLEVEL=1
#CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-S>
#CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-S>

https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=old&openssl=1.1.1d&guideline=5.6 生成的 CipherString 对我不起作用

但以下工作正常 CipherString = DEFAULT:@SECLEVEL=1

最后要检查的是 openssl.cnf 的路由,因为在我的情况下,我输入的是 Ubuntu 18.04:

openssl 版本 -d OPENSSLDIR: "/usr/lib/ssl"

在这条路径中还有另一个https://docs.microsoft.com/en-us/dotnet/core/compatibility/cryptography/5.0/default-cipher-suites-for-tls-on-linux的openssl.cnf

mv /usr/local/ssl/openssl.cnf /usr/local/ssl/openssl.cnf.bak ln -s /usr/local/ssl/openssl.cnf /etc/ssl/openssl.cnf

重启api或应用程序以查看更改非常重要

【讨论】:

  • 这是一个非常糟糕的主意,根本没有解决方案。修复需要弱密码而不是削弱安全性的服务
  • @PanagiotisKanavos 如果您阅读文章 docs.microsoft.com/en-us/dotnet/core/compatibility/cryptography/… ".NET,在 Linux 上,现在在通过 SslStream 类或更高级操作执行 TLS/SSL 时尊重默认密码套件的 OpenSSL 配置,例如作为通过 HttpClient 类的 HTTPS。当未显式配置默认密码套件时,Linux 上的 .NET 使用严格限制的允许密码套件列表“因此,我将安全性调整为 TLSv1.2 及更高版本以解决它跨度>
  • 文档并没有说你认为他们做了什么。他们说 .NET Core 5 使用更安全的密码密码。您启用了较弱的密码。真正的解决方案是修复服务以使用更强的密码
  • 换个说法,为什么你坚持弱化客户而不是加强服务?
  • 使用 asp.net core 3.1 客户端和来自 linux 上的 curl 命令可以正常工作。但是在 asp.net core 5.0 中不起作用,因为在 5.0 中,默认密码仅限于目前认为是强大且安全的密码
猜你喜欢
  • 2020-12-06
  • 2021-11-10
  • 1970-01-01
  • 2020-07-19
  • 2020-11-15
  • 2020-05-19
  • 2021-05-03
  • 2018-11-14
  • 1970-01-01
相关资源
最近更新 更多