1

I have the following Java program (Servlet) on Netbeans 8 where I am making an HTTP GET and sending some security credentials. I am running into the most common error that I found many people discussing. It used to run fine when I was using HTTP but now the server to which the URL is pointing to has been on HTTPS. Here is the error on Netbeans's Apache Tomcat or TomEE Log:

================================================================================= My Error:

01-Oct-2014 14:00:08.497 SEVERE [http-apr-8080-exec-8] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [My_API_Call] in context with path [/testapi] threw 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
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1917)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:301)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:295)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1369)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:156)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:925)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:860)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1043)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1343)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1371)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1355)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1511)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1439)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
    at My_API_Call.sendHttpRequest(My_API_Call.java:129)
    at My_API_Call.Group_GET(My_API_Call.java:82)
    at My_API_Call.doGet(My_API_Call.java:60)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:526)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1078)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:655)
    at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:277)
    at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2381)
    at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2370)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
Caused by: 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.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
    at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
    at sun.security.validator.Validator.validate(Validator.java:260)
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1351)
    ... 39 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:145)
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
    at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
    ... 45 more

================================================================================== My Program:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.sql.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.crypto.Mac;
import org.apache.commons.codec.binary.Hex;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.json.JSONObject;

@WebServlet("/My_API_Call")
public class My_API_Call extends HttpServlet {
       public My_API_Call() {
        super();
       }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         response.setContentType("text/html;charset=UTF-8");
        // Allocate a output writer to write the response message into the network socket
        PrintWriter out = response.getWriter();
        // Write the response message, in an HTML page
        try {
            out.println("<!DOCTYPE html>");  // HTML 5
            out.println("<html><head>");
            out.println("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>");
            out.println("<h3>JSON Response</h3>");
            out.println(new Gson().toJson(API_GET()));
            out.println("<head><title>My API Call</title></head>");
            out.println("<body>");
            // Tabulate the request information 
            out.println("</body></html>");
        }   
        finally {
            out.close();  // Always close the output writer
        }
    }
    public static Object API_GET() throws IOException {
        String accessKey = "myaccesskey";
        String secretKey = "mysecretkey";
        String uRLCppList = "https://myapichecking.mydomain.com/webservice/testinggroup/133";
        String method = "GET";
        java.util.Date currentTime = new java.util.Date();
        SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        String dateTimeString = sdf.format(currentTime);
        String signature = generateSignature(method, secretKey, dateTimeString);
        String authorization = accessKey + ":" + signature;
        Map<String, String> params = new HashMap<String, String>();
        String[] result = sendHttpRequest(uRLCppList, "POST", params, dateTimeString, authorization);
        return result;
    }
    public static String[] sendHttpRequest(String requestUrl, String method, Map<String, String> params, String dateTimeString, String authorization) throws IOException {
        List<String> response = new ArrayList<String>();
        StringBuffer requestParams = new StringBuffer();
        if (params != null && params.size() > 0) {
            Iterator<String> paramIterator = params.keySet().iterator();
            while (paramIterator.hasNext()) {
                String key = paramIterator.next();
                String value = params.get(key);
                requestParams.append(URLEncoder.encode(key, "UTF-8"));
                requestParams.append("=").append(URLEncoder.encode(value, "UTF-8"));
                requestParams.append("&");
            }
        }
        URL url = new URL(requestUrl);
        URLConnection urlConn = url.openConnection();
        urlConn.setRequestProperty("accept", "application/json");
        urlConn.setRequestProperty("datetime", dateTimeString);
        urlConn.setRequestProperty("authorization", authorization);
        urlConn.setUseCaches(false);
        // the request will return a response
        urlConn.setDoInput(true);

        if ("POST".equals(method)) {
            // set request method to POST
            urlConn.setDoOutput(true);
        } else {
            // set request method to GET
            urlConn.setDoOutput(false);
        }
        if ("POST".equals(method) && params != null && params.size() > 0) {
            OutputStreamWriter writer = new OutputStreamWriter(urlConn.getOutputStream());
            writer.write(requestParams.toString());
            writer.flush();  
        }
        // reads response, store line by line in an array of Strings
        BufferedReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));

        String line = "";
        while ((line = reader.readLine()) != null) {
            response.add(line);
        }
        reader.close();
        return (String[]) response.toArray(new String[0]);
    }
    public static String generateSignature(String method, String secretKey, String dateTimeString) {        
        String cs = String.format("%s\n\n\n%s\n\n\n", method, dateTimeString);
        String signature = createSignature(cs, secretKey);
        return  signature;
    }
    public static String createSignature(String stringIn, String scretKey) {
        String fixedData = stringIn.replace('\n', (char)10);
        // Calculate the hash of the information
        String digest = hmacSha1(scretKey, fixedData);
        return digest;
    } 
    public static String hmacSha1(String key, String value) {
        try {
            // Get an hmac_sha1 key from the raw key bytes
            byte[] keyBytes = key.getBytes("iso-8859-1");           
            SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");
            // Get an hmac_sha1 Mac instance and initialize with the signing key
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(signingKey);
            // Compute the hmac on input data bytes
            byte[] rawHmac = mac.doFinal(value.getBytes("iso-8859-1"));
            //  Covert array of Hex bytes to a String
            return Base64.encode(rawHmac);
           //return new String(hexBytes, "UTF-8");
        } 
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

I have referred to the following threads but most of them keeps on talking about installing the certificate into the trust store. And I have already installed the certificate on the machine where the aforementioned URL is referring to "https://myapichecking.mydomain.com/webservice/testinggroup/133" . Say for example it is referring to an IP XX.XX.X.XXX, so I have installed the certificate over there but still getting the same error.

1) Resolving javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed Error?

2)http://www.java-samples.com/showtutorial.php?tutorialid=210

Any idea what's going wrong with my API call ?

Community
  • 1
  • 1
John
  • 1,210
  • 5
  • 23
  • 51

1 Answers1

2

There is a lot that could be going wrong here. SSL issues can be tricky to figure out exactly, but here are some steps for you to try. Hopefully, one of them will work out for you...

First, is the remote server's certificate valid? Is the certificate expired? Is it signed by a well known Certificate Authority (CA)?

If the remote server's certificate is not signed by a well known CA are you sure that certificate is in your truststore? I use keytool -list -v -keystore <truststore file name> to see what certificates are in your truststore. You can also pipe that information out to a file if needed: keytool -list -v -keystore <truststore file name> > truststore.txt It is a bit easier to read then in a terminal.

Are you sure you're running your application with the correct truststore? Unless you're running of the default truststore that ships with java, you will need to use the '-Djavax.net.ssl.trustStore="" -Djavax.net.ssl.trustStorePassword="changeit"' virtual machine option.

If you do not have the remote server certificate in your truststore, I recommend using a java tool called InstallCert to create a new truststore that will have that certificate in it. Then reference the new truststore with the virtual machine options above.

Security Notes:

It is not recommended to change the default truststore that ships with java. Copy it, add certificates as needed to the new copy and then reference the new copy via virtual machine options.

When accepting certificates that are self-signed or signed by a CA that is not well known, there are always potential security issues, just know who/where you're getting the certificate from.

hooknc
  • 4,854
  • 5
  • 31
  • 60