While accessing a https web service using Java, I got this problem. I have tried many methods to solve this problem. Here I am presenting the best method which solved my problem.

Why this error

Indicates that the client and server could not negotiate the desired level of security. The connection is no longer usable. Java program is not able to verify its certificate. May be the certificate is not from trusted store. So we have to add that certificate to keystore, Then it will believe that SSL certificate of the web service can be trusted.

The default keystore for Java is <Java%Home>\jre\lib\security\cacerts
Whenever Java program tries to connect any site using SSL connectivity, JRE will check with above keystore

Check using SSLPoke

Use SSLPoke.java to verify connection. If you don't get SSL Connectivity Exception, then you can continue with below program.

Add Certificate

Now we have to add certificate to <Java%Home>\jre\lib\security\cacerts . Download InstallCert.java. It will add certificates to your Java%Home>\jre\lib\security\jssecacerts . I have modified InstallCert.java file to modify <Java%Home>\jre\lib\security\cacerts. Once new cacerts file created, you need to replace <Java%Home>\jre\lib\security\cacerts with this file. Before replacing, just take backup of <Java%Home>\jre\lib\security\cacerts.  

Observe below code. You can change highlighted values as per your requirement 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class InstallCert {

    /* You can change these variables based on your requirements*/
    // enter domain
    public static String HOST = "www.facebook.com";
    // Enter port
    public static int PORT = 443;
    // Enter pasephrase 
    public static String PASEPHRASE_STR = "changeit";
    // Enter output file here
    public static String OUTPUT_FILE = "D://cacerts";
        

    public static void main(String[] args) throws Exception {
        char[] passphrase = PASEPHRASE_STR.toCharArray();
        
        char SEP = File.separatorChar;
        File dir = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security");
        File file = new File(dir, "cacerts");
        if (file.isFile() == false) {
            file = new File(dir, "cacerts");
        }
        
        System.out.println("Loading KeyStore " + file.getCanonicalPath() + "...");
        InputStream in = new FileInputStream(file);
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(in, passphrase);
        in.close();

        SSLContext context = SSLContext.getInstance("TLS");
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
        SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
        context.init(null, new TrustManager[] { tm }, null);
        SSLSocketFactory factory = context.getSocketFactory();

        System.out.println("Opening connection to " + HOST + ":" + PORT + "...");
        SSLSocket socket = (SSLSocket) factory.createSocket(HOST, PORT);
        socket.setSoTimeout(10000);
        try {
            System.out.println("Starting SSL handshake...");
            socket.startHandshake();
            socket.close();
            System.out.println();
            System.out.println("No errors, certificate is already trusted");
        } catch (SSLException e) {
            System.out.println();
            e.printStackTrace(System.out);
        }

        X509Certificate[] chain = tm.chain;
        if (chain == null) {
            System.out.println("Could not obtain server certificate chain");
            return;
        }

        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        System.out.println();
        System.out.println("Server sent " + chain.length + " certificate(s):");
        System.out.println();
        MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        for (int i = 0; i < chain.length; i++) {
            X509Certificate cert = chain[i];
            System.out.println(" " + (i + 1) + " Subject " + cert.getSubjectDN());
            System.out.println("   Issuer  " + cert.getIssuerDN());
            sha1.update(cert.getEncoded());
            System.out.println("   sha1    " + toHexString(sha1.digest()));
            md5.update(cert.getEncoded());
            System.out.println("   md5     " + toHexString(md5.digest()));
            System.out.println();
        }

        System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
        String line = reader.readLine().trim();
        int k;
        try {
            k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
        } catch (NumberFormatException e) {
            System.out.println("KeyStore not changed");
            return;
        }

        X509Certificate cert = chain[k];
        String alias = HOST + "-" + (k + 1);
        ks.setCertificateEntry(alias, cert);

        OutputStream out = new FileOutputStream(OUTPUT_FILE);
        ks.store(out, passphrase);
        out.close();

        System.out.println();
        System.out.println(cert);
        System.out.println();
        System.out.println("Added certificate to keystore 'cacerts' using alias '" + alias + "'");
    }

    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

    private static String toHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 3);
        for (int b : bytes) {
            b &= 0xff;
            sb.append(HEXDIGITS[b >> 4]);
            sb.append(HEXDIGITS[b & 15]);
            sb.append(' ');
        }
        return sb.toString();
    }

    private static class SavingTrustManager implements X509TrustManager {

        private final X509TrustManager tm;
        private X509Certificate[] chain;

        SavingTrustManager(X509TrustManager tm) {
            this.tm = tm;
        }

        public X509Certificate[] getAcceptedIssuers() {
            throw new UnsupportedOperationException();
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            throw new UnsupportedOperationException();
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            this.chain = chain;
            tm.checkServerTrusted(chain, authType);
        }
    }

}

1 comment:

  1. This app was not an SSL application. However, this tool called a ssl-based web api from within the app. Typically, using keytool to adapt SSL cert to JVM overcomes this type of problem. In my situation, the problem was that the website only sent the cert and the intermediary Certificate, not the main CA. Applying this JVM option resolved the issue. Another explanation might be an out-of-date JDK version. I was using jdk version 1.8.0_60, and simply switching to the current version resolved the certificate problem. By the way, if you are into gaming and want to read latest news about it, visit this page of PC Magazine site.


    ReplyDelete

Blogroll

Popular Posts