title: SMTP client stopped working after updating to netcore 3.1
body: <p>After updating my asp.net-core web API project from 2.2 to 3.1 I'm getting following exception, when trying to send an e-mail via the <code>System.Net.Mail.SmtpClient</code>:</p>
  
  <pre><code>- 2020-02-09 14:40:26.8163  15  ( SmtpClient.OnSendCompleted =&gt; &lt;&gt;c__DisplayClass78_0.&lt;SendMailAsync&gt;b__0 =&gt; SmtpClient.HandleCompletion )  -  Error Failed to send E-Mail. -- ( Exception: System.Net.Mail.SmtpException: Failure sending mail.
   ---&gt; 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 ---
  </code></pre>
  
  <p>The API's endpoints are configured via the Kestrel-section in appsettings.json</p>
  
  <pre><code>  "Kestrel": {
      "EndPoints": {
        "Http": {
          "Url": "http://myapidomain:80"
        },
        "Https": {
          "Url": "https://www.myapidomain:443",
          "Certificate": {
            "Path": &lt;path to pfx file&gt;,
            "Password": &lt;password&gt;
          }
        }
      }
    },
  </code></pre>
  
  <p>This is how I use the <code>SmtpClient</code>:</p>
  
  <pre><code>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;
  }
  </code></pre>
  
  <p>The above code works when I switch back to the <code>aspnetcore2.2</code> version, so I doubt this is an issue with the certificate itself (I'm using exactly the same .pfx file for both). Another important hint might be that it actually works on <code>aspnetcore3.1</code> when debugging locally with a self-signed development certificate on a Windows machine. In production, where above code fails, I'm running the API on Ubuntu. </p>
  
  <p>My guess is that either something has changed with how certificates are handled in asp.net-core 3.x. or that the Windows libraries behave differently. If needed, I can also post both Startup.cs files.</p>
  
  <p>Any suggestions get more details on the actual cause or how to fix this?</p>
  
  <p><strong>Update</strong></p>
  
  <p>As suggested by Adam, I've switched to MailKit's implementation of <code>SmtpClient</code>. The error still persists in production, but I managed to get a bit more details on the error using the <code>client.ServerCertificateValidationCallback</code>. I get following log prints, when inspecting the callback's <code>ChainStatus</code> property:</p>
  
  <pre><code>X509ChainStatusFlags: RevocationStatusUnknown
  StatusInformation: unable to get certificate CRL
  </code></pre>
  
  <p>Could someone explain why the disability to get a certificate CRL causes my connection to fail? How do I fix this?</p>
  
  <p><strong>Update 2</strong></p>
  
  <p>Also, when I set <code>client.CheckCertificateRevocation = false</code> I get following error:</p>
  
  <pre><code>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 
  </code></pre>
  
  <p><strong>Update 3</strong> </p>
  
  <p>It appears that the netcore framework can't find the required ca certificates, because I get exactly the same error message as in Update 2, when I run following command on the Ubuntu machine:</p>
  
  <pre><code>openssl s_client -connect smtp.gmail.com:465
  --&gt; Verify return code: 20 (unable to get local issuer certificate)
  </code></pre>
  
  <p>However, when I explicitly specify the ca-certificate storage path validation passes.</p>
  
  <pre><code>openssl s_client -CApath /etc/ssl/certs/ -connect smtp.gmail.com:465
  --&gt; Verify return code: 0 (ok)
  </code></pre>
  
  <p>So I guess the final question is: <strong>How do I get my aspnet-core web API to locate Ubuntu's ca-certificate storage?</strong></p>
  
  <p><strong>Update 4</strong></p>
  
  <p>I've reported this issue <a href="https://github.com/dotnet/runtime/issues/31514#issuecomment-583967138" rel="nofollow noreferrer">here</a>, maybe this issue will be addressed eventually by the dotnet team. As a (hopefully) temporary workaround I ended up validating the certificate's thumprint manually.</p>