[RESOLVED] javax.net.ssl.SSLHandshakeException@: SSL-2.1: Unknown error (-202) during Resty.json() call

,

Hi,

I struggle bootstrapping a working setup to achieve REST API calls with ej.library.iot:restclient:1.1.0.

but no luck so far.

I added 2 Amazon-Root-CA-1.crt, DigiCert-Global-Root-G2.crt resources, referenced as application resources in .list.
The loading of these seems ok, the SSL Context configuration seems fine too, but at the end of the day I get Unknown error (-202) when calling Resty.json("https://discovery.googleapis.com/discovery/v1/apis").

  • Notice that it works fine with "https://postman-echo.com/get".
  • It fails as well with other REST APIs, I tried Gravatar API for instance.
  • Using the same endpoint in the provided MicroEJ/Example-IOT/ssl-rest-headless fails also.

I’m running the code on Simulator in eval with stm32f7508-dk:M5QNX_eval:2.2.0.

Gradle dependencies:

dependencies {
    implementation("ej.api:edc:1.3.7")

    implementation("ej.library.iot:restclient:1.1.0")
    implementation("ej.library.eclasspath:httpsclient:1.3.0")

    microejVee("com.microej.veeport.st.stm32f7508-dk:M5QNX_eval:2.2.0")
}

Full app code to reproduce the issue:

public class Main {
    public static void main(String[] args) throws GeneralSecurityException, IOException {
        String[] certificates = new String[] { "Amazon-Root-CA-1.crt", "DigiCert-Global-Root-G2.crt" };
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null, null);
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
        for (String certificateRes : certificates) {
            String certificatePath = "/certificates/" + certificateRes;
            System.out.println("Processing certificate: " + certificatePath);
            try (InputStream inputStream = Main.class.getResourceAsStream(certificatePath)) {
                Certificate certificate = certificateFactory.generateCertificate(inputStream);
                System.out.println("\t- Certificate: " + certificate.getType() + " " + certificate.getPublicKey());
                trustStore.setCertificateEntry(certificateRes, certificate);
            }
        }
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
        trustManagerFactory.init(trustStore);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        if (trustManagers.length != 1 && !(trustManagers[0] instanceof X509TrustManager)) {
            throw new GeneralSecurityException();
        }
        X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
        for (TrustManager manager : trustManagers) {
            System.out.println("Trust manager: " + manager.toString());
        }

        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(null, trustManagers, null);
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
        System.out.println("SSLSocketFactory: " + sslSocketFactory);

        Resty httpClient = new Resty();
        JSONResource works = httpClient.json("https://postman-echo.com/get");
        System.out.println("JSON res working: " + works.toString());
        // Exception in thread "main" @T:javax.net.ssl.SSLHandshakeException@: SSL-2.1: Unknown error (-202)
        JSONResource fails = httpClient.json("https://discovery.googleapis.com/discovery/v1/apis");
        System.out.println("JSON res failing: " + fails.toString());
    }
}

The full exception stacktrace:

Exception in thread "main" @T:javax.net.ssl.SSLHandshakeException@: SSL-2.1: Unknown error (-202)
	at java.lang.Throwable.fillInStackTrace(Throwable.java:82)
	at java.lang.Throwable.<init>(Throwable.java:37)
	at java.lang.Exception.<init>(Exception.java:18)
	at java.io.IOException.<init>(IOException.java:18)
	at javax.net.ssl.SSLException.<init>(SSLException.java:39)
	at javax.net.ssl.SSLHandshakeException.<init>(SSLHandshakeException.java:38)
	at com.is2t.support.net.ssl.SSLSocketSupport.initialClientHandShake(SSLSocketSupport.java:120)
	at com.is2t.support.net.ssl.SSLSocketImpl.doHandShake(SSLSocketImpl.java:162)
	at com.is2t.support.net.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:141)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:238)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:96)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:626)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:133)
	at ej.rest.web.AbstractResource.fill(AbstractResource.java:53)
	at ej.rest.web.Resty.fillResourceFromURL(Resty.java:390)
	at ej.rest.web.Resty.doGET(Resty.java:338)
	at ej.rest.web.Resty.json(Resty.java:187)
	at ej.rest.web.Resty.json(Resty.java:175)
	at net.opatry.rest.Main.main(Main.java:51)
	at java.lang.MainThread.run(Thread.java:914)
	at java.lang.Thread.runWrapper(Thread.java:387)

Hello @Olivier,

I can reproduce the TLS handshake error on the connection to discovery.googleapis.com when using the same trust store (Amazon-Root-CA-1.crt & DigiCert-Global-Root-G2.crt).
Yet, I am able to connect if I replace DigiCert-Global-Root-G2.crt by GTS-Root-R1.crt which seems to be the root CA used by discovery.googleapis.com.

You can download it from Google’s PKI repository: Google Trust Services | Repository (Download CA certificates > Root CAs > GTS Root R1).
Or more systematically, for any website, with your favorite browser:


Select the root CA (in this case GTS Root R1) and export to .crt file.

Also, I am taking the opportunity to suggest you to use our SslContextBuilder from ssl-util.
It simplifies the creation of SSLContext using certificates & private keys from application resources or a filesystem for example.

For your use case:

SslContextBuilder builder = new SslContextBuilder();
builder.addServerCertificate("/certificates/Amazon Root CA 1.crt");
builder.addServerCertificate("/certificates/GTS Root R1.crt");
SSLContext sslContext = builder.build();

We have an internal task to update our IoT examples to use this.

Best regards,
Rémy

1 Like

Indeed, it now works when using the proper root certificate :+1:

I wasn’t aware of ssl-util so far, I’ll take a look.

Thanks for your quick & accurate guidance.

1 Like