将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 ---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内核3.x中证书的处理方式的变化,这两种情况都发生了变化。或者Windows库的行为不同。如果需要,我也可以发布两个Startup.cs文件。
任何建议都能得到更多关于实际原因的详细信息,或者如何解决这个问题?
更新
正如Adam所建议的,我已经切换到MailKit的SmtpClient实现。该错误在生产中仍然存在,但我使用client.ServerCertificateValidationCallback获得了有关错误的更多详细信息。在检查回调的ChainStatus属性时,我得到以下日志打印:
X509ChainStatusFlags: RevocationStatusUnknown
StatusInformation: unable to get certificate CRL有人能解释一下为什么无法获得证书CRL会导致我的连接失败吗?我该怎么解决这个问题?
更新2
另外,当我设置client.CheckCertificateRevocation = 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。
发布于 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
并在应用程序启动期间将整个链添加到框架的证书存储区
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);
});
}发布于 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采用者的首选库。
https://stackoverflow.com/questions/60137640
复制相似问题