Asp.net core SMTP客户端在更新到netcore 3.1后停止工作

Asp.net core SMTP客户端在更新到netcore 3.1后停止工作,asp.net-core,openssl,ssl-certificate,smtpclient,asp.net-core-3.1,Asp.net Core,Openssl,Ssl Certificate,Smtpclient,Asp.net Core 3.1,在将我的asp.net核心web API项目从2.2更新到3.1之后,当我尝试通过System.net.mail.SmtpClient发送电子邮件时,出现以下异常: - 2020-02-09 14:40:26.8163 15 ( SmtpClient.OnSendCompleted => <>c__DisplayClass78_0.<SendMailAsync>b__0 => SmtpClient.HandleCompletion ) - Error

在将我的asp.net核心web API项目从2.2更新到3.1之后,当我尝试通过
System.net.mail.SmtpClient
发送电子邮件时,出现以下异常:

- 2020-02-09 14:40:26.8163  15  ( SmtpClient.OnSendCompleted => <>c__DisplayClass78_0.<SendMailAsync>b__0 => SmtpClient.HandleCompletion )  -  Error Failed to send E-Mail. -- ( Exception: System.Net.Mail.SmtpException: Failure sending mail.
 ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
   at System.Net.Security.SslStream.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
   at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Security.SslStream.ThrowIfExceptional()
   at System.Net.Security.SslStream.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslStream.EndProcessAuthentication(IAsyncResult result)
   at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
   at System.Net.Mail.SmtpConnection.ConnectAndHandshakeAsyncResult.TlsStreamAuthenticateCallback(IAsyncResult result)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source)
   at System.Net.Mail.SmtpConnection.ConnectAndHandshakeAsyncResult.End(IAsyncResult result)
   at System.Net.Mail.SmtpTransport.EndGetConnection(IAsyncResult result)
   at System.Net.Mail.SmtpClient.ConnectCallback(IAsyncResult result)
   --- End of inner exception stack trace ---
using (var client = new SmtpClient(_options.MailServiceHost, _options.MailServicePort))
{
    client.EnableSsl = true;
    client.ClientCertificates.Add(_certificate);
    client.DeliveryMethod = SmtpDeliveryMethod.Network;
    client.UseDefaultCredentials = false;
    client.Credentials = new NetworkCredential(_options.MailAccountName, _options.MailAccountPassword);

    var mail = new MailMessage
    {
        From = new MailAddress(author),
        Body = body,
        Subject = subject,
        Sender = new MailAddress(author),
        SubjectEncoding = Encoding.UTF8,
        BodyEncoding = Encoding.UTF8,
        HeadersEncoding = Encoding.UTF8
    };

    mail.Headers.Add("Content-Type", "content=text/html; charset=\"UTF-8\"");
    mail.To.Add(receiver);
    mail.ReplyToList.Add(author);

    _logger.LogInformation($"Sending mail to: {receiver}");
    await client.SendMailAsync(mail);
    return true;
}
当我切换回
aspnetcore2.2
版本时,上面的代码可以正常工作,因此我怀疑这是证书本身的问题(我使用的是完全相同的.pfx文件)。另一个重要提示可能是,当在Windows计算机上使用自签名开发证书进行本地调试时,它实际上在
aspnetcore3.1
上工作。在生产中,如果上面的代码失败,我将在Ubuntu上运行API

我的猜测是,asp.net-core 3.x中证书的处理方式发生了变化。或者Windows库的行为不同。如果需要,我还可以发布两个Startup.cs文件

有什么建议可以获得更多关于实际原因或如何解决这一问题的详细信息吗

更新

正如Adam所建议的,我已经切换到MailKit的
SmtpClient
实现。该错误在生产中仍然存在,但我使用
客户端.ServerCertificateValidationCallback
获得了有关该错误的更多详细信息。检查回调的
ChainStatus
属性时,我会得到以下日志打印:

X509ChainStatusFlags: RevocationStatusUnknown
StatusInformation: unable to get certificate CRL
有人能解释为什么无法获得证书CRL会导致我的连接失败吗?我该如何解决这个问题

更新2

另外,当我设置
client.checkCertificatereJournal=false
时,我得到以下错误:

X509ChainStatusFlags: PartialChain
StatusInformation: unable to get local issuer certificate
Subject: CN=smtp.gmail.com, O=Google LLC, L=Mountain View, S=California, C=US 
Issuer: CN=GTS CA 1O1, O=Google Trust Services, C=US 
更新3

netcore框架似乎找不到所需的ca证书,因为当我在Ubuntu机器上运行以下命令时,我收到的错误消息与Update 2中的错误消息完全相同:

openssl s_client -connect smtp.gmail.com:465
--> Verify return code: 20 (unable to get local issuer certificate)
但是,当我显式指定ca证书存储路径时,验证通过

openssl s_client -CApath /etc/ssl/certs/ -connect smtp.gmail.com:465
--> Verify return code: 0 (ok)
因此,我想最后一个问题是:如何让我的aspnet核心web API定位Ubuntu的ca证书存储?

更新4


我已经报告了这个问题,也许这个问题最终会被dotnet团队解决。作为一种(希望是)临时解决办法,我最终手动验证了证书的thumprint。

因此,如果您查看文档,您可以看到它现在被标记为过时。我真的很惊讶,你没有得到一个编译器错误,你应该得到,但这可能是明天的问题

Microsoft建议使用MailKit库。链接:-

我鼓励您使用Mailkit库重新实现代码,而不是使用
System.Net.Mail.SmtpClient()
。除了没有过时之外,MailKit是一个非常容易使用的库,开发人员和贡献者投入了大量的时间和精力

这是使图书馆过时并推荐MailKit的原因

SmtpClient及其类型的网络设计不佳


有趣的事实:.Net Core 1.0没有访问
System.Net.Mail
命名空间的权限,之后又添加了支持,但是当
System.Net.Mail
在.Net Core中可用时,
MailKit
是早期.Net Core采用者的首选库。

我终于明白了为什么这在Linux上停止工作。netcore3.1框架不从操作系统的ca证书文件夹中读取,而是从

~/.dotnet/corefx/cryptography/x509stores/ca

因此,应用程序不信任任何证书。因此,我从下载了Google的PEM示例文件,并将其转换为P7B文件,以确保整个证书链保持完整

openssl crl2pkcs7-nocrl-certfile google-roots.pem-out google roots.p7b

并在应用程序启动期间将整个链添加到框架的证书存储中

using (var store = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
{
    store.Open(OpenFlags.ReadWrite);

    options.CertificateChainFiles.ToList().ForEach(file =>
    {
        var collection = new X509Certificate2Collection();
        collection.Import(file);
        store.AddRange(collection);
    });
}

不幸的是,切换到MailKit并没有解决这个问题。但是,我设法获得了有关错误的一些附加信息(请参阅我更新的问题)。我知道这个答案不是完整的解决方案,希望它有所帮助。我为您感到这些SSL问题很痛苦。如果我不得不猜测.NETCore3与您从中升级的任何版本之间的区别,那么默认的TLS版本可能会有所不同。你能用一个普通的旧客户端连接到SMTP服务吗?是的,我按照的说明进行了操作,它成功了。事实上,我也在尝试在生产中使用Google的SMTP服务。好吧,有一个区别:示例中的客户端使用端口587(
auth on,tls on
)发送消息,但MailKit的客户端(
useSsl:true
client.SslProtocols
使用不同的标志组合)无法这样做,因为它获得System.IO.IOException:由于意外的数据包格式,握手失败。ubuntu客户端使用端口465失败。他们只需要删除email=P
using (var store = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
{
    store.Open(OpenFlags.ReadWrite);

    options.CertificateChainFiles.ToList().ForEach(file =>
    {
        var collection = new X509Certificate2Collection();
        collection.Import(file);
        store.AddRange(collection);
    });
}