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

问题描述

我在 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,CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request,Boolean async,CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.GetHttp2ConnectionAsync(HttpRequestMessage request,CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request,Boolean doRequestAuth,CancellationToken cancellationToken)
         at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
         at System.Net.Http.DecompressionHandler.SendAsync(HttpRequestMessage request,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 emitTelemetryStartStop,CancellationToken cancellationToken)

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

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,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,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),Server hello (2):
* TLSv1.2 (IN),Certificate (11):
* TLSv1.2 (IN),Server finished (14):
* TLSv1.2 (OUT),Client key exchange (16):
* TLSv1.2 (OUT),TLS change cipher,Change cipher spec (1):
* TLSv1.2 (OUT),Finished (20):
* TLSv1.2 (IN),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 核心 3.1 密码(28 个套件)

enter image description here

客户端 Hello 上的 Asp.net 核心 5.0 密码(9 个套件)

enter image description here

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

我解决了这个问题 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或应用程序以查看更改非常重要

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...