ASP.NET linux 上的 CORE 5.0 TLS 1.2 问题但 asp.net 上的核心 3.1 正在运行

ASP.NET CORE 5.0 TLS 1.2 issue on linux but on asp.net core 3.1 are working

我在 asp.net 核心 5.0 和 linux 中遇到 TLS 1.2 问题。 它仅在 asp.net 核心 5.0 中发生,相同的代码 运行 在 asp.net 核心 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)

我在下方分享了一个控制台应用程序,您应该了解这个问题。 不幸的是,我无法共享应用程序的凭据,并且出于保密协议的原因,也无法共享正在使用其 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

Asp.net Client Hello 上的核心 3.1 密码(28 套件)

Asp.net Client Hello 上的核心 5.0 密码(9 套件)

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

在好的情况下,客户端提供一堆密码,服务器将选择密码套件:TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)(数据包 6 -> ServerHello) 在 5.0 中,默认密码被限制为目前被认为是强而安全的密码。以上不在列表中

我基本运行变成了https://docs.microsoft.com/en-us/dotnet/core/compatibility/cryptography/5.0/default-cipher-suites-for-tls-on-linux

我解决了这个 post https://askubuntu.com/questions/1233186/ubuntu-20-04-how-to-set-lower-ssl-security-level and https://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 version -d OPENSSLDIR: "/usr/lib/ssl"

在这条路上还有 openssl.cnf of https://docs.microsoft.com/en-us/dotnet/core/compatibility/cryptography/5.0/default-cipher-suites-for-tls-on-linux

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

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