javax.net.ssl.SSLHandshakeException:No appropriate protocol. How to force Java to use TLSv1.2 for sending mail?

Viewed 16152

I am trying to use JavaMailSender to send mails in spring boot, but I am getting this error:

javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate). Failed messages: javax.mail.MessagingException: Could not convert socket to TLS;
  nested exception is:
    javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

After doing some reading, I found it is because we are using TLSv1 or TLSv1.1 which is outdated, and we should use v1.2 or higher. I tried to add ssl.protocols property in application.yml with value TLSv1.2, but it does not seem to work. Here is my application.yml:

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: ******@gmail.com
    password: *******
    protocol: smtp
    tls: true
    properties.mail.smtp:
      auth: true
      ssl.trust: smtp.gmail.com
      starttls.required: true
      starttls.enabled: true
      ssl.protocols: TLSv1.2

and here is the method that sends the mail:

public void sendEmail(String to, String subject, String body) {
    logger.info("Sending mail to : " + to);
    SimpleMailMessage mail = new SimpleMailMessage();
    mail.setTo(to);
    mail.setSubject(subject);
    mail.setText(body);

    try {
        javaMailSender.send(mail);
        logger.info("Mail sent to " + to);
    } catch (Exception e) {
        logger.error("Could not send mail to " + to + ". Exception : " + e.getMessage());
    }
}

I updated JavaMailSender version but it didn't fix the issue. The only thing that worked was removing TLSv1 and TLSv1.1 from jdk.tls.disabledAlgorithms as suggested in this answer, but it is only a temporary fix. How can make the mail sender use TLSv1.2 or higher? Is there anything wrong in the way I am defining the ssl.protocols property in application.yml?

This is the java version I am using:

openjdk version "1.8.0_292"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.292-b10, mixed mode)

and the spring boot starter mail version:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
    <version>2.5.3</version>
</dependency>
3 Answers

I recently answered an almost identical question.

As you guessed, your problem will be related with a change introduced in the JDK in order to disable by default insecure TLS protocol versions:

security-libs/javax.net.ssl
āžœ Disable TLS 1.0 and 1.1

TLS 1.0 and 1.1 are versions of the TLS protocol that are no longer considered secure and have been superseded by more secure and modern versions (TLS 1.2 and 1.3).

These versions have now been disabled by default. If you encounter issues, you can, at your own risk, re-enable the versions by removing "TLSv1" and/or "TLSv1.1" from the jdk.tls.disabledAlgorithms security property in the java.security configuration file.

As you can see in the bug description, the change has been backported to different JDK versions, the one you are using among them.

In order to solve the problem you can edit your java.security configuration file and remove TLSv1 and/or TLSv1.1 from the jdk.tls.disabledAlgorithms security property.

In addition, you can explicitly include the required TLS protocol in your configuration properties:

props.put("mail.smtp.ssl.protocols", "TLSv1.2");

I honestly do not understand why Spring Boot is not applying this configuration, it seems you defined it right. You can check it reviewing the source code of the library when describing mail properties configuration and the different accepted mail properties, and as described in the Spring Boot documentation as well. Please, try defining the property like this:

spring:
  mail:
    properties:
      # other properties you need
      "mail.smtp.ssl.protocols": "TLSv1.2"

In order to discard some problem maybe related to YAML formatting, you can try set this property explicitly, just for testing, in JavaMaiSenderImpl:

public void sendEmail(String to, String subject, String body) {
    logger.info("Sending mail to : " + to);
    SimpleMailMessage mail = new SimpleMailMessage();
    mail.setTo(to);
    mail.setSubject(subject);
    mail.setText(body);

    try {
        // Just for validating the solution
        JavaMailSenderImpl javaMailSenderImpl = (JavaMailSenderImpl)javaMailSender;
        // Please, put a breakpoint here and debug the configured
        // properties, it will provide you a valuable information
        // about the actual properties that have been applied      
javaMailSenderImpl.getJavaMailProperties().put("mail.smtp.ssl.protocols", "TLSv1.2");

        javaMailSender.send(mail);
        logger.info("Mail sent to " + to);
    } catch (Exception e) {
        logger.error("Could not send mail to " + to + ". Exception : " + e.getMessage());
    }
}

You can do it by setting System.setProperty("mail.smtp.ssl.protocols", protocols);. For example:

String protocols = String.join(" ", 
    SSLContext
        .getDefault()
        .getSupportedSSLParameters()
        .getProtocols()
);

System.setProperty("mail.smtp.ssl.protocols", protocols);

In any case, this config it's working without any configuration

  mail:
    host: smtp.gmail.com
    port: 587
    username: ***@gmail.com
    password: P4ssw0rd
    properties:
      mail:
        smtp:
          starttls:
            enable: true
            required: true
      auth: true

I have a similar problem when I use application.properties the error I was getting is:

-dev.miku.r2dbc.mysql.client.MySqlConnectionClosedException: Connection unexpectedly closed -d.m.r.mysql.client.ReactorNettyClient : Error: No appropriate protocol (protocol is disabled or cipher suites are inappropriate) -12-16 14:48:30.287 WARN 27232 --- [actor-tcp-nio-2] d.m.r.mysql.client.ReactorNettyClient : Connection has been closed by peer 2021-12-16 14:48:30.288 ERROR 27232 --- [actor-tcp-nio-2] reactor.core.publisher.Operators : Operator called default onErrorDropped

HERE IS MY SOLUTION:

Just one line solved the issue:

spring.r2dbc.properties.ssl= false
Related