Search code examples
c#.netopensslsmtpclientamazon-linux-2

SSL_ERROR_SSL cannot send email using C# SmtpClient from Amazon Linux EC2 instance after upgrading from dotnet 3.1 to 6.0


I am trying to send email using C# dotnet 6.0 from Amazon Linux server. The code looks something like this:

public class EmailSender
{
    private readonly SmtpOption _smtpOption;
    public EmailSender(IConfiguration configuration)
    {
        _smtpOption = configuration.GetSection("Smtp").Get<SmtpOption>();
    }

    public void SendEmail()
    {

        string defaultBody =
            "<h1>Amazon SES Test</h1>" +
            "<p>This email was sent through the " +
            "<a href='https://aws.amazon.com/ses'>Amazon SES</a> SMTP interface " +
            "using the .NET System.Net.Mail library.</p>";

        // Create and build a new MailMessage object
        MailMessage message = new()
        {
            IsBodyHtml = true,
            From = new MailAddress(_smtpOption.From, _smtpOption.FromName),
            Subject = _smtpOption.Subject,
            Body = _smtpOption.Body ?? defaultBody
        };

        message.To.Add(new MailAddress(_smtpOption.To));

        using (var client = new SmtpClient(_smtpOption.Host, _smtpOption.Port))
        {
            // Pass SMTP credentials
            client.Credentials =
                new NetworkCredential(_smtpOption.Username, _smtpOption.Password);

            // Enable SSL encryption
            client.EnableSsl = _smtpOption.EnableSsl;

            // Try to send the message. Show status in console.
            try
            {
                Console.WriteLine("Attempting to send email...");
                client.Send(message);
                Console.WriteLine("Email sent!");
            }
            catch (Exception ex)
            {
                Console.WriteLine("The email was not sent.");
                Console.WriteLine("Error message: " + ex.Message);
                Console.WriteLine(ex);
            }
        }
    }
}


public class SmtpOption
{
    public string From { get; set; }
    public string FromName { get; set; }
    public string To { get; set; }
    public string Host { get; set; }
    public int Port { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
    public bool EnableSsl { get; set; }
}

This code works fine on my windows development environment. But when I run this on Amazon Linux Ec2 instance, I am getting this error.

Attempting to send email...
The email was not sent.
Error message: Authentication failed, see inner exception.
System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
 ---> Interop+OpenSsl+SslException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL.
 ---> Interop+Crypto+OpenSslCryptographicException: error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error
   --- End of inner exception stack trace ---
   at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, ReadOnlySpan`1 input, Byte[]& sendBuf, Int32& sendCount)
   at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteSslContext& context, ReadOnlySpan`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions)
   --- End of inner exception stack trace ---
   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
   at System.Net.Security.SslStream.AuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions)
   at System.Net.Security.SslStream.AuthenticateAsClient(String targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, Boolean checkCertificateRevocation)
   at System.Net.TlsStream.AuthenticateAsClient()
   at System.Net.Mail.SmtpConnection.GetConnection(String host, Int32 port)
   at System.Net.Mail.SmtpTransport.GetConnection(String host, Int32 port)
   at System.Net.Mail.SmtpClient.GetConnection()
   at System.Net.Mail.SmtpClient.Send(MailMessage message)
   at EmailTester.EmailSender.SendEmail()

The code was also working in dotnet 3.1 in Amazon Linux. This started happening after we updated to 6.0

I think this is related to this breaking change introduced in dotnet 5.0 here (https://learn.microsoft.com/en-us/dotnet/core/compatibility/cryptography/5.0/default-cipher-suites-for-tls-on-linux) This link describes how to resolve this by editing openssl.cnf file however, It is not working for me.

ALso the last paragraph there says this:

On the Red Hat Enterprise Linux, CentOS, and Fedora distributions, .NET applications default to the cipher suites permitted by the system-wide cryptographic policies. On these distributions, use the crypto-policies configuration instead of changing the OpenSSL configuration file.

I am thinking since Amazon Linux is based on RHEL this might be relavant to my issue. Unfortunately the exact steps to solve this in RHEL or Amazon Linux is not clear from the link.

How do I change system-wide cryptographic policies in Amazon Linux?

Any help is appreciated. Thanks in advance.


Solution

  • OP Here. I was able to solve this issue. If someone lands here in the future this is the solution that worked for me.

    The config file change in this link was not working but I found this comment in github. The changes that are different from MS link are CipherString, Ciphersuites and MinProtocol values

    Please DONOT just paste this at the bottom of the file. the openssl_config = default_conf part should be in global area at the top. Other section can go at the bottom.

    openssl_conf = default_conf
    
    [default_conf]
    ssl_conf = ssl_sect
    
    [ssl_sect]
    system_default = system_default_sect
    
    [system_default_sect]
    CipherString = @SECLEVEL=2:kEECDH:kRSA:kEDH:kPSK:kDHEPSK:kECDHEPSK:-aDSS:-3DES:!DES:!RC4:!RC2:!IDEA:-SEED:!eNULL:!aNULL:!MD5:-SHA384:-CAMELLIA:-ARIA:-AESCCM8
    Ciphersuites = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256
    MinProtocol = TLSv1.2
    

    You can find the location for openssl.cnf file with this command openssl version -d in my case it was /etc/pki/tls

    Also if you need to do this without manually editing the file say for headless deployment. Use these commands from the folder containing openssl.cnf file.

    yum install crudini -y #or use sudo apt-get install crudini -y
    crudini --set openssl.cnf "" openssl_conf default_conf
    crudini --set openssl.cnf default_conf ssl_conf ssl_sect
    crudini --set openssl.cnf ssl_sect system_default system_default_sect
    crudini --set openssl.cnf system_default_sect CipherString '@SECLEVEL=2:kEECDH:kRSA:kEDH:kPSK:kDHEPSK:kECDHEPSK:-aDSS:-3DES:!DES:!RC4:!RC2:!IDEA:-SEED:!eNULL:!aNULL:!MD5:-SHA384:-CAMELLIA:-ARIA:-AESCCM8'
    crudini --set openssl.cnf system_default_sect Ciphersuites 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256'
    crudini --set openssl.cnf system_default_sect MinProtocol TLSv1.2
    

    or for elasticbeanstalk add this file commands.config to the .ebextension folder.

    commands:
      01_install_crudini:
        command: 'yum install crudini -y'
      02_configure_openssl:
        command: |
          crudini --set openssl.cnf "" openssl_conf default_conf
          crudini --set openssl.cnf default_conf ssl_conf ssl_sect
          crudini --set openssl.cnf ssl_sect system_default system_default_sect
          crudini --set openssl.cnf system_default_sect CipherString '@SECLEVEL=2:kEECDH:kRSA:kEDH:kPSK:kDHEPSK:kECDHEPSK:-aDSS:-3DES:!DES:!RC4:!RC2:!IDEA:-SEED:!eNULL:!aNULL:!MD5:-SHA384:-CAMELLIA:-ARIA:-AESCCM8'
          crudini --set openssl.cnf system_default_sect Ciphersuites 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256'
          crudini --set openssl.cnf system_default_sect MinProtocol TLSv1.2
        cwd: /etc/pki/tls
        test: "crudini --version"