June 20, 2013

InstallCert and Java 7

When communicating with a server using a self-signed SSL using a Java based client, this custom certificate must be known by the callee side of the communication. This can be done manually by adding the certificate to the client JVM.

To simplify this process, one can use a simple tool called InstallCert as described here and here. This application was originally written for Java 5 / Java 6. While this solution still works, when you try to run this application through Java 7, you will get an additional error message like this:

javax.net.ssl.SSLException: java.lang.UnsupportedOperationException
 at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
 at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1886)
 at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1844)
 at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1827)
 at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1346)
 at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
 at org.vangen.auth.InstallCert.main(InstallCert.java:81)
Caused by: java.lang.UnsupportedOperationException
 at org.vangen.auth.InstallCert$SavingTrustManager.getAcceptedIssuers(InstallCert.java:168)
 at sun.security.ssl.AbstractTrustManagerWrapper.checkAlgorithmConstraints(SSLContextImpl.java:926)
 at sun.security.ssl.AbstractTrustManagerWrapper.checkAdditionalTrust(SSLContextImpl.java:872)
 at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:814)
 at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1323)
 at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:153)
 at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
 at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
 at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
 at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
 at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
 ... 2 more

To avoid this error, you will need to change how accepted issuers are returned. The original code threw an exception for this method. The Java 7 implementation calls this method, which was not the case with earlier versions of Java. The solution to this problem is to simply return an empty array like this:

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

By doing this small change, the application will no longer return an error upon execution. The full application code will then be:

package org.vangen.auth;

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 {

    public static void main(final String[] args) throws Exception {
        String host;
        int port;
        char[] passphrase;
        if ((args.length == 1) || (args.length == 2)) {
            final String[] c = args[0].split(":");
            host = c[0];
            port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
            final String p = (args.length == 1) ? "changeit" : args[1];
            passphrase = p.toCharArray();
        } else {
            System.out.println(
                    "Usage: java InstallCert <host>[:port] [passphrase]");
            return;
        }

        File file = new File("jssecacerts");
        if (file.isFile() == false) {
            final char SEP = File.separatorChar;
            final File dir = new File(System.getProperty("java.home")
                    + SEP + "lib" + SEP + "security");
            file = new File(dir, "jssecacerts");
            if (file.isFile() == false) {
                file = new File(dir, "cacerts");
            }
        }

        System.out.println("Loading KeyStore " + file + "...");
        final InputStream in = new FileInputStream(file);
        final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(in, passphrase);
        in.close();

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

        System.out.println("Opening connection to "
                + host + ":" + port + "...");
        final 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 (final SSLException e) {
            System.out.println();
            e.printStackTrace(System.out);
        }

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

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

        System.out.println();
        System.out.println("Server sent " + chain.length + " certificate(s):");
        System.out.println();
        final MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        final MessageDigest md5 = MessageDigest.getInstance("MD5");
        for (int i = 0; i < chain.length; i++) {
            final 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]");
        final String line = reader.readLine().trim();
        int k;
        try {
            k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
        } catch (final NumberFormatException e) {
            System.out.println("KeyStore not changed");
            return;
        }

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

        final OutputStream out = new FileOutputStream(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(final byte[] bytes) {
        final 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(final X509TrustManager tm) {
            this.tm = tm;
        }

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

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

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

25 comments:

  1. Thank you very much for this. It helped me =]

    ReplyDelete
    Replies
    1. dongtam
      game mu
      http://nhatroso.net/
      http://nhatroso.com/
      nhac san cuc manh
      tổng đài tư vấn luật
      dịch vụ thành lập công ty trọn gói
      văn phòng luật
      tổng đài tư vấn pháp luật
      thành lập công ty
      http://we-cooking.com/
      chém gió
      trung tâm ngoại ngữ

      Lại một tia chớp nữa, xuyên qua thân thể Kim Long của hắn, Nhạc Thành kêu lên một tiếng đau đớn.

      Trong nháy mắt, Nhạc Thành đã bị lôi điện oanh tạc dữ dội.

      - Âm Dương Kiếm.

      - Ám Ảnh kiếm.

      - Toàn Phong Trảm.

      - tru ma chưởng.

      - Phong Lôi chấn.

      Nhạc Thành dùng từng đạo lực lượng công kích ngạnh kháng lại, thi triển toàn lực.

      Ba canh giờ sau, uy lực của thiên kiếp vẫn không hề suy giảm, liên tục áp chế ở trên đỉnh đầu của Nhạc Thành.

      - Tại sao vẫn chưa dứt?

      Thú ma ở phía xa xa cất tiếng nghi hoặc hỏi.

      Delete
  2. Hi,
    Nice article!
    What if using this code for a particular domain: for Java 6 it throws the exception "java.net.SocketException: Connection reset" and for Java 7 it performs well?

    Regards
    Reis

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. what is it when run it:
    "Usage: java InstallCert [:port] [passphrase]"

    ReplyDelete
  5. in this command just mention your hostname:portnumber
    Example --> java InstallCert xx.yy.zz:portnumber

    ReplyDelete
  6. Hi guys,
    I did pass 192.168.1.30:5222
    I am getting the following exception

    javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
    at sun.security.ssl.InputRecord.handleUnknownRecord(InputRecord.java:671)
    at sun.security.ssl.InputRecord.read(InputRecord.java:504)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:927)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
    at smacktest.InstallCert.main(InstallCert.java:76)
    Could not obtain server certificate chain



    javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?

    ReplyDelete
  7. Hi Guys,

    It works for me, but now the server is throwing "http response code 500" and not able to access the web service through httpclient. kindly support.

    ReplyDelete
  8. Thanks much ! Really appreciate, saved me lots of time !

    ReplyDelete
  9. Thanks a lot!
    What about Java 8? Do you already have experience with it?

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. This worked for me. I plan to publish the same on my blog too: cubicrace.com

    ReplyDelete
  12. Thanks for the post..it helped me resolving the cert issue

    ReplyDelete
  13. Hii,

    I got the following error :

    javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
    at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1991)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1104)
    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 de.db.mayday.mqtt.client.InstallCert.main(InstallCert.java:108)


    An idea ?

    Thanks

    ReplyDelete
  14. Switch to JRE8, it will help - check this - https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https

    ReplyDelete
  15. Thanks for the reply, I'm imported the certificate and can see it in the cacerts but still getting the PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target exception.

    ReplyDelete
  16. Hello. Please Help by explaining the above code . What it is actually do with IpAdress and Port number.

    ReplyDelete