首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SMTP客户端在更新到netcore 3.1后停止工作

SMTP客户端在更新到netcore 3.1后停止工作
EN

Stack Overflow用户
提问于 2020-02-09 14:26:39
回答 2查看 2.1K关注 0票数 1

将ASP.NET核心web API项目从2.2更新到3.1后,当尝试通过System.Net.Mail.SmtpClient发送电子邮件时,将出现以下异常

代码语言:javascript
复制
- 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 ---

API的端点是通过appsettings.json中的Kestrel部分配置的。

代码语言:javascript
复制
  "Kestrel": {
    "EndPoints": {
      "Http": {
        "Url": "http://myapidomain:80"
      },
      "Https": {
        "Url": "https://www.myapidomain:443",
        "Certificate": {
          "Path": <path to pfx file>,
          "Password": <password>
        }
      }
    }
  },

我就是这样使用SmtpClient

代码语言:javascript
复制
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内核3.x中证书的处理方式的变化,这两种情况都发生了变化。或者Windows库的行为不同。如果需要,我也可以发布两个Startup.cs文件。

任何建议都能得到更多关于实际原因的详细信息,或者如何解决这个问题?

更新

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

代码语言:javascript
复制
X509ChainStatusFlags: RevocationStatusUnknown
StatusInformation: unable to get certificate CRL

有人能解释一下为什么无法获得证书CRL会导致我的连接失败吗?我该怎么解决这个问题?

更新2

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

代码语言:javascript
复制
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完全相同的错误消息:

代码语言:javascript
复制
openssl s_client -connect smtp.gmail.com:465
--> Verify return code: 20 (unable to get local issuer certificate)

但是,当我显式指定ca证书存储路径时,验证会通过。

代码语言:javascript
复制
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。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-07-06 06:41:19

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

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

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

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

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

代码语言:javascript
复制
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);
    });
}
票数 0
EN

Stack Overflow用户

发布于 2020-02-09 15:04:59

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

微软建议使用MailKit库。链接:努基特 - GitHub

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

这是使库过时的原因,并推荐MailKit是

SmtpClient及其类型网络设计很差。

有趣的事实:.Net Core1.0没有访问System.Net.Mail命名空间,并且在.NET Core中添加了支持,但是当System.Net.Mail在.NET Core中可用时,MailKit是早期.NET Core采用者的首选库。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60137640

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档