19

I want access a SOAP webservice url having https hosted in a remote vm. I am getting an exception while accessing it using HttpURLConnection.

Here's my code:

import javax.net.ssl.*;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
 * Created by prasantabiswas on 07/03/17.
 */
public class Main
{
    public static void main(String [] args)
    {
        try
        {
            URL url = new URL("https://myhost:8913/myservice/service?wsdl");
            HttpURLConnection http = null;

            if (url.getProtocol().toLowerCase().equals("https")) {
                trustAllHosts();
                HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
                https.setHostnameVerifier(DO_NOT_VERIFY);
                http = https;
            } else {
                http = (HttpURLConnection) url.openConnection();
            }
            String SOAPAction="";
//            http.setRequestProperty("Content-Length", String.valueOf(b.length));
            http.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
            http.setRequestProperty("SOAPAction", SOAPAction);
            http.setRequestMethod("GET");
            http.setDoOutput(true);
            http.setDoInput(true);
            OutputStream out = http.getOutputStream();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };

    private static void trustAllHosts() {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[] {};
            }

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

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

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection
                    .setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

I'm getting the following exception:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
    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:1509)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:914)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
    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.getOutputStream0(HttpURLConnection.java:1283)
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1258)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:250)
    at Main.main(Main.java:35)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints
    at sun.security.ssl.AbstractTrustManagerWrapper.checkAlgorithmConstraints(SSLContextImpl.java:1055)
    at sun.security.ssl.AbstractTrustManagerWrapper.checkAdditionalTrust(SSLContextImpl.java:981)
    at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:923)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1491)
    ... 18 more

Tried different solution from the google search, Non of them worked. I want to avoid using keytool because I will be running my tests on different vm.

Does anyone have any solution for this?

Prasanta Biswas
  • 761
  • 2
  • 14
  • 26
  • 1
    Try it with a higher java version. You might be using an outdated java version that doesn't support the encryption types used. – Tschallacka Mar 15 '17 at 10:16
  • I am using JAVA 8 – Prasanta Biswas Mar 15 '17 at 10:17
  • You are doing the SOAP request on your own private server? On your test VM add the key to the trusted keys. For the public soap you don't need that since there are CA's that can vouch for the autenticty. – Tschallacka Mar 15 '17 at 10:19
  • SOAP service is hosted on server that is using a self signed certificate. I am running my test from my local as well as any vm. I don't want to add the key to truststore in each vm. Is there any other way arround? – Prasanta Biswas Mar 15 '17 at 10:23
  • @PrasantaBiswas self-signed certificates aren't accepted without manual addition to the trust store, browsers(chrome,firefox,etc) don't accept these as well – niceman Mar 15 '17 at 10:26
  • Try the option number two from http://stackoverflow.com/questions/2893819/telling-java-to-accept-self-signed-ssl-certificate – Tschallacka Mar 15 '17 at 10:27
  • Tried this, didn't work for me. – Prasanta Biswas Mar 15 '17 at 10:28
  • @PrasantaBiswas can you edit the question saying why didn't it work for you ? – niceman Mar 15 '17 at 10:29

5 Answers5

27

Using X509ExtendedTrustManager instead of X509TrustManager() solved the problem. Here's the example:

public void trustAllHosts()
    {
        try
        {
            TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509ExtendedTrustManager()
                    {
                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers()
                        {
                            return null;
                        }

                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
                        {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
                        {
                        }

                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException
                        {

                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException
                        {

                        }

                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException
                        {

                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException
                        {

                        }

                    }
            };

            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            // Create all-trusting host name verifier
            HostnameVerifier allHostsValid = new  HostnameVerifier()
            {
                @Override
                public boolean verify(String hostname, SSLSession session)
                {
                    return true;
                }
            };
            // Install the all-trusting host verifier
            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
        }
        catch (Exception e)
        {
            log.error("Error occurred",e);
        }
    }
Prasanta Biswas
  • 761
  • 2
  • 14
  • 26
7

Edit : Understand the vulnerability this would cause before using it. This is by no means recommended for production use.

The best way is to create a dummy trustmanager that trusts everything.

 TrustManager[] dummyTrustManager = new TrustManager[] { new X509TrustManager() {
      public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return null;
      }

      public void checkClientTrusted(X509Certificate[] certs, String authType) {
      }

      public void checkServerTrusted(X509Certificate[] certs, String authType) {
      }
    } };

Then use the dummy trustmanager to initialize the SSL Context

SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, dummyTrustManager, new java.security.SecureRandom());

Finally use the SSLContext to open connection

HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

    URL url = new URL("https://myhost:8913/myservice/service?wsdl");

This question has already been answered here in more detail Java: Overriding function to disable SSL certificate check

Update:

Above issue is due to certificate signature algorithm not being supported by Java. As per this post, later releases of Java 8 have disabled md5 algorithm.

To enable md5 support, locate java.security file under <jre_home>/lib/security and locate the line (535)

jdk.certpath.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, 

and remove MD5

Community
  • 1
  • 1
Monish Sen
  • 1,773
  • 3
  • 20
  • 32
  • If you look at my code , I have already done that. Didn't work. It is working fine when the url is https://google.com – Prasanta Biswas Mar 15 '17 at 10:57
  • @PrasantaBiswas even though you bypass certificate validation, the connection still has to perform a SSL handkshake and encrypt the contents if you mean to use https. It seems the certificate is invalid. Can you download the certificate using broswer and share the certificate details, especially the signature algorithm – Monish Sen Mar 15 '17 at 12:05
  • @PrasantaBiswas as a quickfix you can try updating the JCE policy jars for java 8, if not already done so. Do reply if this works for you – Monish Sen Mar 15 '17 at 12:10
  • I have downloaded the certificate which is expired but still chrome can access the url. It uses MD5 RSA algorithm. Now how do I use this certificate to bypass ssl handshake? – Prasanta Biswas Mar 15 '17 at 15:58
  • @PrasantaBiswas see updated answer. FYI you cannot bypass ssl handshake, it is pre-requisite to establish the connection – Monish Sen Mar 16 '17 at 06:33
  • I know this. But I don't want to get into someone else's machine and change stuff. Instead I used X509ExtendedTrustManager that solved the problem. Anyway thanks for you help. – Prasanta Biswas Mar 16 '17 at 12:53
  • Added this extra code and it worked HostnameVerifier allHostsValid = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); – Raghavendra Bankapur Jul 02 '19 at 10:43
5

Try with Apache HTTP client, this works for me.

SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustStrategy() {
     public boolean isTrusted(final X509Certificate[] chain, String authType) throws CertificateException {
          return true;
     }
});
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build());

CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

// GET or POST request with the client
...
dereck
  • 499
  • 1
  • 10
  • 20
1

Instead of using HttpsURLConnection.setDefaultSSLSocketFactory and your own implementation of TrustManager or X509ExtendedTrustManager, you can use TrustManagerFactory with a KeyStore with the certificate that issued the certificate you need to trust (for a self-signed certificate, this is the same as the host certificate) and call HttpsURLConnection.setSSLSocketFactory on the specific instance. This is both less code and avoids the security problems with trusting all HTTPS certicates.

In main:

            if (url.getProtocol().toLowerCase().equals("https")) {
                HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
                https.setSSLSocketFactory(createSSLSocketFactory());
                http = https;
            }

The method createSSLSocketFactory looks like this:

    private static SSLSocketFactory createSSLSocketFactory() {
         File crtFile = new File("server.crt");
         Certificate certificate =          CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));

         KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
         keyStore.load(null, null);
         keyStore.setCertificateEntry("server", certificate);

         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
         trustManagerFactory.init(keyStore);

         SSLContext sslContext = SSLContext.getInstance("TLS");
         sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

         return sslContext.getSocketFactory();
    }
Johannes Brodwall
  • 7,673
  • 5
  • 34
  • 28
0

Another way if using httpClient from Apache:

public static CloseableHttpClient getCloseableHttpClient()
{
    CloseableHttpClient httpClient = null;
    try {
        httpClient = HttpClients.custom().setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                .setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy()
                {
                    public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException
                    {
                        return true;
                    }
                }).build()).build();

    } catch (KeyManagementException e) {
        LOGGER.error("KeyManagementException in creating http client instance", e);
    } catch (NoSuchAlgorithmException e) {
        LOGGER.error("NoSuchAlgorithmException in creating http client instance", e);
    } catch (KeyStoreException e) {
        LOGGER.error("KeyStoreException in creating http client instance", e);
    }
    return httpClient;
}

And then: HttpPost post = new HttpPost("your uri");

CloseableHttpResponse response = getCloseableHttpClient.execute(post);

And of course all the try-catches blocks (or use try with resources)