SMTP-клиент перестал работать после обновления до netcore 3.1

После обновления моего проекта веб-API asp.net-core с 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 настраиваются через раздел Kestrel в appsettings.json.

  "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 для обоих). Другой важный aspnetcore3.1 может заключаться в том, что он действительно работает на aspnetcore3.1 при локальной отладке с помощью самозаверяющего сертификата разработки на компьютере с Windows. На производстве, где приведенный выше код не работает, я запускаю API на Ubuntu.

Я предполагаю, что либо что-то изменилось с тем, как обрабатываются сертификаты в asp.net-core 3.x. или что библиотеки Windows ведут себя по-другому. При необходимости я также могу опубликовать оба файла Startup.cs.

Любые предложения получить более подробную информацию о фактической причине или как это исправить?

Обновить

По предложению Адама, я переключился на реализацию SmtpClient в 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, потому что я получаю точно такое же сообщение об ошибке, как в обновлении 2, когда я запускаю следующую команду на компьютере с Ubuntu:

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)

Итак, я предполагаю, что последний вопрос: как мне получить мой веб-API aspnet-core, чтобы найти хранилище ca-сертификата Ubuntu?

Всего 1 ответ


Итак, если вы посмотрите документацию для SmtpClient (), то увидите, что она помечена как устаревшая. Я на самом деле очень удивлен, что вы не получили ошибку компилятора, вы должны были это сделать, но это, вероятно, проблема завтрашнего дня.

Microsoft рекомендует использовать библиотеку MailKit. Ссылки: Nuget - GitHub

Я бы посоветовал вам заново реализовать свой код, используя библиотеку Mailkit, а не System.Net.Mail.SmtpClient() . Помимо того, что MailKit не устарела, она является удивительно простой библиотекой, которую разработчики и авторы вкладывают в нее много времени и думают.

Это причина сделать библиотеку устаревшей и рекомендовать MailKit

SmtpClient и его сеть типов плохо разработаны

Интересный факт: .Net Core 1.0 не имел доступа к пространству имен System.Net.Mail и после него была добавлена ​​поддержка, однако к тому времени, когда System.Net.Mail был доступен в .NET Core, MailKit была предпочтительной библиотекой на ранних этапах. NET Core принимает.


Есть идеи?

10000