Published 30 Sep, 2022

Java - SSLHandshakeException: Certificate Unknown (Java Spring Boot & Android)

Category Java
Modified : Nov 30, 2022
90

I have a Spring Boot API that runs locally, with a self-signed certificate, using the HTTPS protocol. Obviously, when I send GET Requests from the browser, I receive the io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown error on the server side, which is normal, because the self-signed is not trusted by the browser. Postman works just fine for GET and POST.

However, I want to send GET requests from an Android client to the Spring API but, even I've used a function to allow all SSL traffic (yes, I know it's not recommended), I still can't send requests to the API, receiving the following output:

I/STATUS: 405
I/MSG: Method Not Allowed

I thought my allowAllSSL() function (HttpsTrustManager class) would solve the issue, because if I remove the function call, I receive the following error, which seems to match the one on the server side:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:239)

Now, you may think that the GET request is not implemented correctly in Spring, but it's not true, since the same GET request works just fine from Postman. I believe that the problem is still linked to the certificate, but I can't figure out what do I need to change. Here is my code:

Spring BOOT Rest Controller

@RestController
@RequestMapping("/post")


public class PostRequest {
    @GetMapping("")
    public String string(@RequestBody ImageRequest newEmployee){
....

The ImageRequest class contains just three private String members.

HttpsTrustManager class (to allow all SSL)

package com.example.androidclient;

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class HttpsTrustManager implements X509TrustManager {
    private static TrustManager[] trustManagers;
    private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};

    @Override
    public void checkClientTrusted(
            X509Certificate[] x509Certificates, String s)
            throws java.security.cert.CertificateException {

    }

    @Override
    public void checkServerTrusted(
            X509Certificate[] x509Certificates, String s)
            throws java.security.cert.CertificateException {

    }

    public boolean isClientTrusted(X509Certificate[] chain) {
        return true;
    }

    public boolean isServerTrusted(X509Certificate[] chain) {
        return true;
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return _AcceptedIssuers;
    }

    public static void allowAllSSL() {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }

        });

        SSLContext context = null;
        if (trustManagers == null) {
            trustManagers = new TrustManager[];
        }

        try {
            context = SSLContext.getInstance("TLS");
            context.init(null, trustManagers, new SecureRandom());
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }

        HttpsURLConnection.setDefaultSSLSocketFactory(context != null ? context.getSocketFactory() : null);
    }
}

Android Request

        HttpsTrustManager.allowAllSSL();
        URL url = new URL("https://192.168.1.106:8443/post");
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
        conn.setRequestProperty("Accept", "application/json");
        conn.setDoOutput(true);
        conn.setDoInput(true);

        JSONObject jsonParam = new JSONObject();
        jsonParam.put("location", "Somewhere");
        jsonParam.put("date", "22.05.2020");
        jsonParam.put("imageBytes", strings[0]);


        Log.i("JSON", jsonParam.toString());
        DataOutputStream os = new DataOutputStream(conn.getOutputStream());
        //os.writeBytes(URLEncoder.encode(jsonParam.toString(), "UTF-8"));
        os.writeBytes(jsonParam.toString());

        os.flush();
        os.close();

        Log.i("STATUS", String.valueOf(conn.getResponseCode()));
        Log.i("MSG", conn.getResponseMessage());

        conn.disconnect();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "ok";
}

Answers

There are 2 suggested solutions here and each one has been listed below with a detailed description. The following topics have been covered briefly such as Java, Ssl, Android, Spring, Spring Boot. These have been categorized in sections for a clear and precise explanation.

32

I have found the solution on my own. Apparently, the Connection.setDoOutput(true) method is working just for POST and PUT requests, but not for GET.

Thus, I have changed my RequestMapping to work on POST, like, this:

@RequestMapping(
    value = "/post",
    produces = "application/json",
    method = RequestMethod.POST)

Now I receive 200 OK.


27

Use this function in your android application.

Please note this will allow all ssl certificates without verification. I would encourage you to follow the recommended approach when dealing with self-signed certificates outlined here: https://developer.android.com/training/articles/security-ssl#java

// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};

// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();