asp.net-core - 更新到 net core 3.1 后 SMTP 客户端停止工作

标签 asp.net-core openssl ssl-certificate smtpclient asp.net-core-3.1

将我的 asp.net-core 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 ---

API 的端点通过 appsettings.json 中的 Kestrel 部分进行配置

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

这就是我使用SmtpClient的方式:

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 实现。该错误在生产中仍然存在,但我设法使用 client.ServerCertificateValidationCallback 获取有关该错误的更多详细信息。在检查回调的 ChainStatus 属性时,我得到以下日志打印:

X509ChainStatusFlags: RevocationStatusUnknown
StatusInformation: unable to get certificate CRL

有人可以解释为什么无法获取证书 CRL 会导致我的连接失败吗?我该如何解决这个问题?

更新2

此外,当我设置 client.CheckCertificateReplication = 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 计算机上运行以下命令时,我收到与更新 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-core Web API 找到 Ubuntu 的 ca 证书存储?

更新4

我已报告此问题 here ,也许这个问题最终会由 dotnet 团队解决。作为(希望)临时解决方法,我最终手动验证了证书的指纹。

最佳答案

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

Microsoft 建议使用 MailKit 库。友情链接:Nuget -GitHub

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

废弃该库并推荐 MailKit 的原因是

SmtpClient and its network of types are poorly designed

有趣的事实:.Net Core 1.0 无法访问 System.Net.Mail 命名空间,并且在之后添加了支持,但是到了 System.Net.Mail 时code> 在 .NET Core 中可用,MailKit 是早期 .NET Core 采用者的首选库。

关于asp.net-core - 更新到 net core 3.1 后 SMTP 客户端停止工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60137640/

相关文章:

c# - ASP.NET Core - 处理 404 和 500 错误

ssl - 如何安装: OpenSSL + WAMP

C: 动态链接 OpenSSL 库时出错

Android 忽略自签名证书

windows - 代码签名服务之间有什么区别?

android - 改造 HTTPS 连接在 Android 中不起作用

C# ASP.NET Core 开火即忘

c# - 创建和验证邀请码/ token

asp.net-core - 如何在 AspNetCore 中仅使用公钥验证使用 x509 签名的 JWT token

c++ - rsa_private_decrypt 返回-1,错误0x0306B067