SMTP-клиент перестал работать после обновления до netcore 3.1 - PullRequest
1 голос
/ 09 февраля 2020

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

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

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

Обновление

Как подсказал Адам, я переключился на реализацию 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, потому что я получаю точно такое же сообщение об ошибке, как в Обновлении 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)

Поэтому я думаю, что последний вопрос: Как мне получить pnet -core веб-API для поиска хранилища CA-сертификатов Ubuntu?

1 Ответ

1 голос
/ 09 февраля 2020

Итак, если вы посмотрите документацию для 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 был предпочтительной библиотекой ранних.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...