scalasslspray-client

Spray-client sendReceive throws SSLHandshakeException


I am trying to make spray-client connect using https to restricted rest api. The problem is that the certificate of the remote server is not registered as trusted, the simple Get() connection is then refused with SSLHandshakeException and I struggle to find any information about how to make this work. This somehow does work from my local machine without a need to change something.

I have found tutorials about how to put the certificate into jvm truststore, however since I am using dokku/docker, AFAIK the jvm instance is container specific (or?). Even though, I may in the future redeploy application on different machines, I'd like to have it defined in the application rather than setting jvm up everytime.

This is the first time I am facing SSL programmatically, so I may make wrong assumptions about how it works. Can you help?


Solution

  • I am not an expert in scala and I have never used spray-client but I will try to help you based on my Java experience.

    You have two options, initialize a SSLContext with a TrustManagerFactory from a keystore with the server certificate (SECURE)

    File keyStoreFile = new File("./myKeyStore");
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
    ks.load(new FileInputStream(keyStoreFile), "keyStorePassword".toCharArray());
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
    tmf.init(ks);
    SSLContext sc = SSLContext.getInstance("TLS");
    sc.init(null, tmf.getTrustManagers(), new java.security.SecureRandom());
    

    or create a Dummy TrustManagerFactory which accepts any certificate (INSECURE)

    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    import javax.net.ssl.X509TrustManager;
    
    public class DummyTrustManager implements X509TrustManager {
    
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    
        /* (non-Javadoc)
        * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], java.lang.String)
        */
        public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
        }
        /* (non-Javadoc)
        * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], java.lang.String)
        */
        public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
        }
    }
    

    initialize SSLContext by this way (it is very similar in spray-client)

    SSLContext sc = SSLContext.getInstance("TLS");
    sc.init(null, new TrustManager[] { new DummyTrustManager() }, new java.security.SecureRandom());
    

    I don't know the Scala syntax but should not be difficult to translate it to you.

    Hope this helps.


    EDIT (suggested by Matej Briškár): The above is the correct approach, however for spray-client it is not that easy. To make sendReceive work with SSL, you need to first establish connection and then pass this connection to sendReceive.

    First create implicit trust manager as described above. For example:

    implicit def sslContext: SSLContext = { 
        val context = SSLContext.getInstance("TLS") 
        context.init(null, Array[TrustManager](new DummyTrustManager), new SecureRandom())
        context
    }
    

    Note that this connection will time out after a while so you may want to change this default behaviour.

    Then you need to establish the connection that will use this implicit like:

    val connection = { 
        Await.result((IO(Http) ? HostConnectorSetup(host, port = 443, sslEncryption = true)).map { case HostConnectorInfo(hostConnector, _) => hostConnector }, timeout.duration) 
    }
    

    Note: host means the URL you are trying to reach. Also timeout is coming from outside of this code snippet.

    And finally you can use sendReceive(connection) to access the SSL encrypted host.


    Note: The original edit had a reference:

    According to discussion online the issue is going to be fixed though.

    However, the discussion is from 2013 and now it's 2016. The problem of needing a connection be made to get SSL working seems still be there. Not sure if the discussion is relevant, any more.