SunCertPathBuilderException: unable to find valid

2020-06-06 07:43发布

问题:

Please can you help. I have a Codenameone app that issues a GET request to a cloud Tomcat 8 server, and expects back some response JSON. Importantly this is a HTTPS call.

When i run the request in Postman it works fine:

https://www.mydomain.co.uk:8443/MyProject/v1/generate_token

The same URL through my browser works and shows as 'Secure' and i can see my certificate details. I have bought a certificate for my SSL/TLS configuration, and seems to function fine in the logs on startup.

In the simulator i get back the following error at the point of reading the response back from the URL call - which i guess must be encrypted:

Exception: javax.net.ssl.SSLHandshakeException - sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1959)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1514)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:961)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
    at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:347)
    at com.codename1.impl.javase.JavaSEPort.getResponseCode(JavaSEPort.java:7591)
    at com.codename1.io.ConnectionRequest.performOperation(ConnectionRequest.java:702)
    at com.codename1.io.NetworkManager$NetworkThread.run(NetworkManager.java:282)
    at com.codename1.impl.CodenameOneThread.run(CodenameOneThread.java:176)

Why should the app be any different to Postman making the call (the Network Monitor window confirms the same URL call) ?

None of the logs are being updated after my call, so nothing to check there. I haven't made any changes to my app (which was working) since moving from http to https.

Here is the CN1 code making the call:

public String fetchTokenIntoStorage(String userName, String password) {
        ConnectionRequest r = new ConnectionRequest();
        r.setUrl(Constants.URL_HOST_PORT + "/MyProject" + Constants.LIVE_OR_TEST
                + "/v1/generate_token");
        r.addRequestHeader("Content-Type", "application/json");
        r.addRequestHeader("userName", userName);
        r.addRequestHeader("password", password);
        r.setHttpMethod("GET");
        r.setFailSilently(false);
        r.setPost(false);
        // show spinning dialog while connecting
        InfiniteProgress prog = new InfiniteProgress();
        Dialog dlg = prog.showInifiniteBlocking();
        r.setDisposeOnCompletion(dlg);
        NetworkManager.getInstance().setTimeout(10000);
        // NetworkManager.getInstance().addErrorListener(new ActionListener() {
        //
        // @Override
        // public void actionPerformed(ActionEvent evt) {
        // MessageBox.showDialogMessage("Unable to connect to server. Please
        // retry later.");
        // }
        // });
        // NetworkManager.getInstance().updateThreadCount(2);
        NetworkManager.getInstance().addToQueueAndWait(r);

        if (r.getResponseData() != null) {
            JSONParser parser = new JSONParser();
            Map<String, Object> json = null;
            try {
                json = parser.parseJSON(new InputStreamReader(new ByteArrayInputStream(r.getResponseData())));
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if (json.get("error") != null) {
                return String.valueOf(json.get("error"));
            }
            JwtRecord record = new JwtRecord();
            record.userId = Integer.parseInt(String.valueOf(json.get("userId")));
            record.jsonWebToken = (String) json.get("jwt");
            record.theme = "LIGHT";
            Storage.getInstance().writeObject("MyToken", record);
            return "";
        }
        if (!r.getResponseErrorMessage().equalsIgnoreCase("")) {
            return r.getResponseErrorMessage();
        } else {
            return "Unable to connect to server. Please check connection.";
        }
    }

Stepping through the code it seems to error just after

NetworkManager.getInstance().addToQueueAndWait(r);

The r.getResponseData() and r.getResponseErrorMessage() are null.

Many thanks

回答1:

This happens if the signing authority isn't recognized by the underlying JDK. Assuming you purchased your certificate from a valid source it's possible that it uses a relatively new root which means you need to use the latest version of JDK 8.

E.g. letsencrypt was only added in JDK 8 update 101.



回答2:

It is now working.

  1. (On cloud tomcat) I made sure the root certificate and the intermediary certificate were in my keystore (as per the links i previously included). I included my .ca-bundle in the keystore for good measure.

  2. (On cloud tomcat) And i noticed i was using an older version of the Apache configuration (lesson learned about relying on older forum posts). Needed to have SSLCACertificateFile pointing to my .ca-bundle file, rather than using SSCertificateChainFile, in my apache .conf file.

  3. It still error on my simulator but works on my iphone, which points (as Shai says) to differing JDK's i expect, so upgraded my laptop to the higher JDK 1.8.171. This didn't in itself make a difference but probably required.

  4. Through digging around i realised that simulators on my laptop needed the above also. So i ended up running the statements below, in command prompt as Administrator, and now my simulator is working.

    cd %java_home%\jre\lib\security

    path=%java_home\bin

    keytool -import -alias comodo -keystore cacerts -file C:\path\ComodoRoot.cer

    keytool -import -alias comodo_intermediate -keystore cacerts -file C:\path\ComodoInter.cer

    keytool -import -alias purchased_cert -keystore cacerts -file C:\path\my_purchased_cert.crt