50

This code connects to a HTTPS site and I am assuming I am not verifying the certificate. But why don't I have to install a certificate locally for the site? Shouldn't I have to install a certificate locally and load it for this program or is it downloaded behind the covers? Is the traffic between the client to the remote site still encrypted in transmission?

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class TestSSL {

    public static void main(String[] args) throws Exception {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = 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) {
            }
        } };
        // Install the all-trusting trust manager
        final 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() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        // Install the all-trusting host verifier
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);

        URL url = new URL("https://www.google.com");
        URLConnection con = url.openConnection();
        final Reader reader = new InputStreamReader(con.getInputStream());
        final BufferedReader br = new BufferedReader(reader);        
        String line = "";
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }        
        br.close();
    } // End of main 
} // End of the class //
Berlin Brown
  • 10,806
  • 35
  • 129
  • 193
  • That radically insecure `TrustManager` implementation doesn't even comply with its own specification. Check the Javadoc. – user207421 Oct 23 '12 at 06:37
  • 5
    @EliteOctagon Don't use this in production code! This TrustManager simply ignores invalid certificates and makes MITM attacks very easy. – binwiederhier Dec 21 '13 at 20:07
  • Thanks @binwiederhier , I'll keep that in mind. Does that even go for URLs that no one will ever see as they are ones only I know of? Can you elaborate on the Man in the Middle attack in this situation? – SoluableNonagon Dec 23 '13 at 22:03
  • 3
    @EliteOctagon It depends on where the packets travel, not whether or not the URL is public. Regarding MITM, I wrote two blog posts about it: http://blog.philippheckel.com/2013/07/01/how-to-use-mitmproxy-to-read-and-modify-https-traffic-of-your-phone/ or http://blog.philippheckel.com/2013/08/04/use-sslsplit-to-transparently-sniff-tls-ssl-connections/ – binwiederhier Dec 27 '13 at 10:23
  • In the posts, you have to install a trusted CA for it to work. With the trust manager above, that's not necessary; so this trust manager is reaaaally dangerous! – binwiederhier Dec 27 '13 at 10:25

6 Answers6

32

The reason why you don't have to load a certificate locally is that you've explicitly chosen not to verify the certificate, with this trust manager that trusts all certificates.

The traffic will still be encrypted, but you're opening the connection to Man-In-The-Middle attacks: you're communicating secretly with someone, you're just not sure whether it's the server you expect, or a possible attacker.

If your server certificate comes from a well-known CA, part of the default bundle of CA certificates bundled with the JRE (usually cacerts file, see JSSE Reference guide), you can just use the default trust manager, you don't have to set anything here.

If you have a specific certificate (self-signed or from your own CA), you can use the default trust manager or perhaps one initialised with a specific truststore, but you'll have to import the certificate explicitly in your trust store (after independent verification), as described in this answer. You may also be interested in this answer.

Community
  • 1
  • 1
Bruno
  • 110,518
  • 24
  • 258
  • 357
  • If we used the browser as an example, typically a browser doesn't ask the user to explicitly install a certificate for each ssl site visited. Does the java https client api support some type of mechanism to download certificate information automatically? – Berlin Brown Oct 23 '12 at 03:27
  • 2
    @BerlinBrown Because the browser has a list of trusted CAs just like Java does. – user207421 Oct 23 '12 at 06:37
  • I just came upon this answer. I don't know if this is so outdated, but you said: `The traffic will still be encrypted, but you're opening the connection to Man-In-The-Middle attacks: ` but if you check this answer: http://stackoverflow.com/a/3075498/1194779 it says otherwise. I'm a bit lost. – Lambasoft May 04 '17 at 07:10
  • 1
    @Lambasoft, that's a consequence of "*you've explicitly chosen not to verify the certificate, with this trust manager that trusts all certificates*" (`checkServerTrusted` does nothing and never throws an exception), which effectively disables authentication of the server. The other answer you link to assumes this was not disabled. – Bruno May 04 '17 at 07:53
  • @Bruno Ohh I see! Thanks a lot for clearing this up. One last huge favor, could you point me to a link where I could learn to correctly write an HTTPS secured connection ? If there's no troubles of course. Thanks again Bruno! – Lambasoft May 04 '17 at 07:56
12

But why don't I have to install a certificate locally for the site?

Well the code that you are using is explicitly designed to accept the certificate without doing any checks whatsoever. This is not good practice ... but if that is what you want to do, then (obviously) there is no need to install a certificate that your code is explicitly ignoring.

Shouldn't I have to install a certificate locally and load it for this program or is it downloaded behind the covers?

No, and no. See above.

Is the traffic between the client to the remote site still encrypted in transmission?

Yes it is. However, the problem is that since you have told it to trust the server's certificate without doing any checks, you don't know if you are talking to the real server, or to some other site that is pretending to be the real server. Whether this is a problem depends on the circumstances.


If we used the browser as an example, typically a browser doesn't ask the user to explicitly install a certificate for each ssl site visited.

The browser has a set of trusted root certificates pre-installed. Most times, when you visit an "https" site, the browser can verify that the site's certificate is (ultimately, via the certificate chain) secured by one of those trusted certs. If the browser doesn't recognize the cert at the start of the chain as being a trusted cert (or if the certificates are out of date or otherwise invalid / inappropriate), then it will display a warning.

Java works the same way. The JVM's keystore has a set of trusted certificates, and the same process is used to check the certificate is secured by a trusted certificate.

Does the java https client api support some type of mechanism to download certificate information automatically?

No. Allowing applications to download certificates from random places, and install them (as trusted) in the system keystore would be a security hole.

Stephen C
  • 632,615
  • 86
  • 730
  • 1,096
6

Use the latest X509ExtendedTrustManager instead of X509Certificate as advised here: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints

    package javaapplication8;

    import java.io.InputStream;
    import java.net.Socket;
    import java.net.URL;
    import java.net.URLConnection;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLEngine;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.X509ExtendedTrustManager;

    /**
     *
     * @author hoshantm
     */
    public class JavaApplication8 {

        /**
         * @param args the command line arguments
         * @throws java.lang.Exception
         */
        public static void main(String[] args) throws Exception {
            /*
             *  fix for
             *    Exception in thread "main" 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
             */
            TrustManager[] trustAllCerts = new TrustManager[]{
                new X509ExtendedTrustManager() {
                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

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

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

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

                    }

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

                    }

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

                    }

                    @Override
                    public void checkServerTrusted(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);
            /*
             * end of the fix
             */

            URL url = new URL("https://10.52.182.224/cgi-bin/dynamic/config/panel.bmp");
            URLConnection con = url.openConnection();
            //Reader reader = new ImageStreamReader(con.getInputStream());

            InputStream is = new URL(url.toString()).openStream();

            // Whatever you may want to do next

        }

    }
Community
  • 1
  • 1
Tarik
  • 9,314
  • 1
  • 19
  • 35
  • 1
    Nuked. Thanks. The code here is much cleaner: https://newfivefour.com/android-trust-all-ssl-certificates.html – Gayan Weerakutti Aug 29 '18 at 18:07
  • 1
    @GayanWeerakutti Could you elaborate on why the code is cleaner? Note that I am using the X509ExtendedTrustManager rather than the X509TrustManager. Have a look at the beginning of my answer for the reason to use the extended flavor of the TrustManager. – Tarik Sep 03 '18 at 10:16
  • I'm referring to its class, as its matter of copy pasting. You may be right about using the `X509ExtendedTrustManager`. BTW class on the link also did work for me. – Gayan Weerakutti Sep 03 '18 at 10:23
1

Java and HTTPS url connection without downloading certificate

If you really want to avoid downloading the server's certificate, then use an anonymous protocol like Anonymous Diffie-Hellman (ADH). The server's certificate is not sent with ADH and friends.

You select an anonymous protocol with setEnabledCipherSuites. You can see the list of cipher suites available with getEnabledCipherSuites.

Related: that's why you have to call SSL_get_peer_certificate in OpenSSL. You'll get a X509_V_OK with an anonymous protocol, and that's how you check to see if a certificate was used in the exchange.

But as Bruno and Stephed C stated, its a bad idea to avoid the checks or use an anonymous protocol.


Another option is to use TLS-PSK or TLS-SRP. They don't require server certificates either. (But I don't think you can use them).

The rub is, you need to be pre-provisioned in the system because TLS-PSK is Pres-shared Secret and TLS-SRP is Secure Remote Password. The authentication is mutual rather than server only.

In this case, the mutual authentication is provided by a property that both parties know the shared secret and arrive at the same premaster secret; or one (or both) does not and channel setup fails. Each party proves knowledge of the secret is the "mutual" part.

Finally, TLS-PSK or TLS-SRP don't do dumb things, like cough up the user's password like in a web app using HTTP (or over HTTPS). That's why I said each party proves knowledge of the secret...

jww
  • 83,594
  • 69
  • 338
  • 732
1

A simple, but not pure java solution, is to shell out to curl from java, which gives you complete control over how the request is done. If you're just doing this for something simple, this allows you to ignore certificate errors at times, by using this method. This example shows how to make a request against a secure server with a valid or invalid certificate, pass in a cookie, and get the output using curl from java.

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

public class MyTestClass
{
  public static void main(String[] args) 
  {
    String url = "https://www.google.com";
    String sessionId = "faf419e0-45a5-47b3-96d1-8c62b2a3b558";

    // Curl options are:
    // -k: ignore certificate errors
    // -L: follow redirects
    // -s: non verbose
    // -H: add a http header

    String[] command = { "curl", "-k", "-L", "-s", "-H", "Cookie: MYSESSIONCOOKIENAME=" + sessionId + ";", "-H", "Accept:*/*", url };
    String output = executeShellCmd(command, "/tmp", true, true);
    System.out.println(output);
  }

  public String executeShellCmd(String[] command, String workingFolder, boolean wantsOutput, boolean wantsErrors)
  {
    try
    {
      ProcessBuilder pb = new ProcessBuilder(command);
      File wf = new File(workingFolder);
      pb.directory(wf);

      Process proc = pb.start();
      BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
      BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));

      StringBuffer sb = new StringBuffer();
      String newLine = System.getProperty("line.separator");
      String s;

      // read stdout from the command
      if (wantsOutput)
      {
        while ((s = stdInput.readLine()) != null)
        {
          sb.append(s);
          sb.append(newLine);
        }
      }

      // read any errors from the attempted command
      if (wantsErrors)
      {
        while ((s = stdError.readLine()) != null)
        {
          sb.append(s);
          sb.append(newLine);
        }
      }

      String result = sb.toString();

      return result;
    }
    catch (IOException e)
    {
      throw new RuntimeException("Problem occurred:", e);
    }
  }


}
Brad Parks
  • 54,283
  • 54
  • 221
  • 287
0

If you are using any Payment Gateway to hit any url just to send a message, then i used a webview by following it : How can load https url without use of ssl in android webview

and make a webview in your activity with visibility gone. What you need to do : just load that webview.. like this:

 webViewForSms.setWebViewClient(new SSLTolerentWebViewClient());
                webViewForSms.loadUrl(" https://bulksms.com/" +
                        "?username=test&password=test@123&messageType=text&mobile="+
                        mobileEditText.getText().toString()+"&senderId=ATZEHC&message=Your%20OTP%20for%20A2Z%20registration%20is%20124");

Easy.

You will get this: SSLTolerentWebViewClient from this link: How can load https url without use of ssl in android webview