Spring RestTemplate: SSL handshake failure

35,325

Solution 1

From javax.net.debug log I can see that you are using Java 7 and the client resolves to TLSv1. From openssl output that your server does not support TLSv1.

TLS ver. 1.1 and 1.2 are disabled in Java 7 by default.

Although SunJSSE in the Java SE 7 release supports TLS 1.1 and TLS 1.2, neither version is enabled by default for client connections. Some servers do not implement forward compatibility correctly and refuse to talk to TLS 1.1 or TLS 1.2 clients. For interoperability, SunJSSE does not enable TLS 1.1 or TLS 1.2 by default for client connections.

Enable TLSv1.1 and TLSv1.2 either by:

  1. JVM argument:

    -Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1
    
  2. Or set the same property from Java code:

    System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,TLSv1");
    
  3. Or install JCE Unlimited Strength policy files for Java 7. I am not 100% sure if this single step would solve the problem although it is always worth to install JCE while it allows JVM to use stronger versions of existing algorithms.

UPDATE 29/Sep/2016:

Order of protocols changed from better to worse (TLS ver. 1.2 to 1) in options 1 and 2.

Solution 2

If you happen to be using Java 7 you need to explicitly tell Java to use TLSv1.2 protocol. Here is an example using Spring XML configuration.

***In any Spring bean (i.e. a controller)***

import org.apache.http.client.HttpClient;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import javax.net.ssl.SSLContext;

@Bean(name="client")
public HttpClient make() throws NoSuchAlgorithmException, KeyManagementException {
    SSLContext sslContext = SSLContexts.custom().build();
    SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
            new String[]{"TLSv1.2", "TLSv1.1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    return HttpClients.custom()
            .setSSLSocketFactory(sslConnectionSocketFactory)
            .build();
}

***In XML configuration files***

<bean id="factory"
      class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
    <property name="httpClient" ref="client"/>
</bean>
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <property name="requestFactory" ref="factory"/>
</bean>

You could do the same without XML with something like this:

RestTemplate restTemplate;

public HttpClient getHttpClient() throws NoSuchAlgorithmException, KeyManagementException {
    SSLContext sslContext = SSLContexts.custom().build();
    SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
            new String[]{"TLSv1.2", "TLSv1.1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    return HttpClients.custom()
            .setSSLSocketFactory(sslConnectionSocketFactory)
            .build();
}

public void setUp() throws Exception {
    restTemplate = new RestTemplate(
            new HttpComponentsClientHttpRequestFactory(
                    getHttpClient()));
    ...
}
Share:
35,325
David
Author by

David

Updated on February 05, 2020

Comments

  • David
    David over 4 years

    I am trying to consume a restful ws with basic auth. I did not import any cert into my keystore. When I use chrome plugin Advance Rest client to test it (using basic auth with base64 encoded username:pass). I can see the response back. So far so good. But when I develop Java code to consume this ws, I get SSL handsake failure:

    org.springframework.web.client.ResourceAccessException: I/O error: 
    Received fatal alert: handshake_failure; nested exception is
    javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:377)
    at test.Rest.main(Rest.java:37) Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at sun.security.ssl.Alerts.getSSLException(Unknown Source)
    at sun.security.ssl.Alerts.getSSLException(Unknown Source)
    

    My question is: if the problem is because I did not import cert to my keystore, then both Java code and plugin should not work together. Here, plugin works but my code does not. What is the reason? There is somethings wrong with my code?

    Bellow is my code

    RestTemplate restTemplate = new RestTemplate();         
     String plainCreds = "username:pass"; 
     byte[] plainCredsBytes = plainCreds.getBytes(Charset.forName("US-ASCII") );     
     byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes); 
     String base64Creds = new String(base64CredsBytes);
    
     HttpHeaders headers = new HttpHeaders(); 
     headers.add("Authorization", "Basic " + base64Creds);
    
     ResponseEntity<String> response =  
              restTemplate.exchange("https://url",HttpMethod.GET,new  
                                      HttpEntity(headers),String.class);
    

    Here is the link to log file:(I have replace my server-name by XXXXXX) http://www.filedropper.com/ssllog

    After running: openssl s_client -showcerts -tls1 -connect host:port

    WARNING: can't open config file: /usr/local/ssl/openssl.cnf
    CONNECTED(00000164)
    8088:error:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number:.\ssl\s3_pkt.c:362:
    ---
    no peer certificate available
    ---
    No client certificate CA names sent
    ---
    SSL handshake has read 5 bytes and written 0 bytes
    ---
    New, (NONE), Cipher is (NONE)
    Secure Renegotiation IS NOT supported
    Compression: NONE
    Expansion: NONE
    No ALPN negotiated
    SSL-Session:
        Protocol  : TLSv1
        Cipher    : 0000
        Session-ID:
        Session-ID-ctx:
        Master-Key:
        Key-Arg   : None
        PSK identity: None
        PSK identity hint: None
        SRP username: None
        Start Time: 1452011344
        Timeout   : 7200 (sec)
        Verify return code: 0 (ok)
    ---
    

    and this is the output when i run command openssl s_client -connect server:port

    WARNING: can't open config file: /usr/local/ssl/openssl.cnf
    CONNECTED(00000164)
    depth=0 C = US, ST = "XXXXXX", L = XXXXXX, O = XXXXXX, OU = xxxxx, CN = XXXXXXXXX.test.intranet, emailAddress = xxxxx@xxxxx
    verify error:num=20:unable to get local issuer certificate
    verify return:1
    depth=0 C = US, ST = "XXXXXXX ", L = XXXXX, O = XXXXXX, OU = xxxxxx, CN = XXXXXXXXX.test.intranet, emailAddress = xxxxx@xxxxx
    verify error:num=21:unable to verify the first certificate
    verify return:1
    ---
    Certificate chain
     0 s:/C=US/ST=XXXXXXX /L=XXXXX/O=XXXXXX/OU=XXXXX/CN=XXXXXX.test.intranet/emailAddress=xxxxx@xxxxx
       i:/DC=intranet/DC=xxxx/CN=XXXXXX DEV Issuing CA
    ---
    Server certificate
    -----BEGIN CERTIFICATE-----
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXX.....
    -----END CERTIFICATE-----
    subject=/C=US/ST=XXXXXXX /L=XXXXX/O=XXXXXX/OU=XXXXX/CN=XXXXXX.test.intranet/emailAddress=xxxxx@xxxxx
    issuer=/DC=intranet/DC=XXX/CN=XXXXX DEV Issuing CA
    ---
    No client certificate CA names sent
    Peer signing digest: SHA512
    Server Temp Key: XXXXX, P-256, 256 bits
    ---
    SSL handshake has read 1895 bytes and written 443 bytes
    ---
    New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
    Server public key is 2048 bit
    Secure Renegotiation IS supported
    Compression: NONE
    Expansion: NONE
    No ALPN negotiated
    SSL-Session:
        Protocol  : TLSv1.2
        Cipher    : ECDHE-RSA-AES128-GCM-SHA256
        Session-ID: 568BF22A5CDBF103155264BBC056B272168AE0777CBC10F055705EB2DD907E5A
        Session-ID-ctx:
        Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        Key-Arg   : None
        PSK identity: None
        PSK identity hint: None
        SRP username: None
        Start Time: 1452012074
        Timeout   : 300 (sec)
        Verify return code: 21 (unable to verify the first certificate)
    ---
    read:errno=0
    
  • David
    David over 8 years
    Thanks Michal for your patient to help me. We find out that the server that hosts rest ws is using jdk8, and my client is using jdk7. So we have downgrade server to jdk7. And i also have to force jdk to use TSLv1.2 by using -Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2. It is working now.
  • David
    David over 8 years
    before trying to use -Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2. i also tried to install JCE Unlimited Strength policy for jdk7. I have replaced files in jdk/jre/lib/security by the files in zip file. But it does not works.
  • Michal Foksa
    Michal Foksa over 8 years
    You are welcome David and thank you for your feedback and appreciation.
  • Bela Vizy
    Bela Vizy over 7 years
    I would turn around the order like this -Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1 This way it will try the better protocols first and fall back to v1 at the end.