web-dev-qa-db-ja.com

Javaおよび証明書をダウンロードしないHTTPS URL接続

このコードはHTTPSサイトに接続し、証明書を検証していないと想定しています。しかし、なぜサイトの証明書をローカルにインストールする必要がないのですか?証明書をローカルにインストールしてこのプログラム用にロードする必要はありませんか、それとも裏側からダウンロードしますか?クライアントからリモートサイトへのトラフィックは、送信中も暗号化されていますか?

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 //
44
Berlin Brown

証明書をローカルにロードする必要がない理由は、すべての証明書を信頼するこの信頼マネージャーを使用して、証明書を検証しないことを明示的に選択したためです。

トラフィックは依然として暗号化されますが、Man-In-The-Middle攻撃への接続を開いています:誰かと密かに通信しているので、それが期待するサーバーなのか、攻撃者である可能性があるのか​​わかりません。

サーバー証明書が、JRE(通常cacertsファイル、JSSEリファレンスガイドを参照)にバンドルされているCA証明書のデフォルトバンドルの一部である既知のCAから提供されている場合、デフォルトのトラストマネージャーを使用できます。ここで何も設定する必要はありません。

特定の証明書(自己署名または独自のCAから)がある場合、デフォルトのトラストマネージャーまたは特定のトラストストアで初期化されたものを使用できますが、トラストストアに証明書を明示的にインポートする必要があります(独立した後 this answer で説明されているとおり)。 this answer にも興味があるかもしれません。

31
Bruno

しかし、なぜサイトの証明書をローカルにインストールする必要がないのですか?

使用しているコードは、チェックを一切行わずに証明書を受け入れるように明示的に設計されています。これは良い習慣ではありません...しかし、それがあなたがやりたいことであれば、(明らかに)あなたのコードが明示的に無視している証明書をインストールする必要はありません。

証明書をローカルにインストールしてこのプログラム用にロードする必要はありませんか、それとも裏側からダウンロードしますか?

いや、いや。上記を参照。

クライアントからリモートサイトへのトラフィックは、送信中も暗号化されていますか?

はい、そうです。ただし、問題は、チェックを行わずにサーバーの証明書を信頼するように指示しているため、実サーバーと通信しているのか、それともふりをしているである他のサイトと通信しているのかわからないことです実サーバーになります。これが問題かどうかは、状況によって異なります。


ブラウザーを例として使用した場合、通常、ブラウザーはユーザーに、訪問したsslサイトごとに証明書を明示的にインストールするよう要求しません。

ブラウザには、信頼済みルート証明書のセットが事前にインストールされています。ほとんどの場合、「https」サイトにアクセスすると、ブラウザーはサイトの証明書が(最終的には証明書チェーンを介して)これらの信頼できる証明書の1つによって保護されていることを確認できます。チェーンの始めにあるブラウザが信頼できる証明書であるとブラウザが認識しない場合(または証明書が古くなっているか、無効または不適切である場合)、警告が表示されます。

Javaも同じように機能します。 JVMのキーストアには信頼できる証明書のセットがあり、同じプロセスを使用して、信頼できる証明書によって証明書が保護されていることを確認します。

Java httpsクライアントAPIは、証明書情報を自動的にダウンロードする何らかのタイプのメカニズムをサポートしていますか?

いいえ。アプリケーションがランダムな場所から証明書をダウンロードし、システムのキーストアに(信頼できるように)インストールすることを許可すると、セキュリティホールになります。

12
Stephen C

ここで推奨されているように、X509Certificateの代わりに最新のX509ExtendedTrustManagerを使用します。 Java.security.cert.CertificateException:証明書はアルゴリズムの制約に適合しません

    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

        }

    }
3
Tarik

証明書をダウンロードせずにJavaおよびHTTPS URL接続

サーバーの証明書のダウンロードを本当に避けたい場合は、Anonymous Diffie-Hellman(ADH)などの匿名プロトコルを使用してください。サーバーの証明書は、ADHや友人とは送信されません。

setEnabledCipherSuitesで匿名プロトコルを選択します。 getEnabledCipherSuitesで利用可能な暗号スイートのリストを見ることができます。

関連:だからこそ、SSL_get_peer_certificate OpenSSLで。 X509_V_OK匿名プロトコルを使用します。これが、交換で証明書が使用されたかどうかを確認する方法です。

しかし、BrunoとStephed Cが述べたように、チェックを回避するか、匿名プロトコルを使用するのは悪い考えです。


別のオプションは、TLS-PSKまたはTLS-SRPを使用することです。サーバー証明書も必要ありません。 (しかし、私はあなたがそれらを使用できるとは思わない)。

こすりは、TLS-PSKはPres-shared Secretであり、TLS-SRPはSecure Remote Passwordであるため、システムで事前にプロビジョニングする必要があります。認証はサーバーのみではなく相互認証です。

この場合、相互認証は、両方の当事者が共有シークレットを知っており、同じプリマスターシークレットに到達するというプロパティによって提供されます。または、一方(または両方)がそうではなく、チャネルのセットアップが失敗します。各当事者は、秘密の知識が「相互」の部分であることを証明します。

最後に、TLS-PSKまたはTLS-SRPは、HTTP(またはHTTPS経由)を使用するWebアプリのようにユーザーのパスワードをせき立てるような愚かなことはしません。それが私が言った理由です各当事者は秘密の知識を証明します...

1
jww

単純だが純粋ではないJavaソリューションは、Javaからcurlにシェルアウトすることです。これにより、リクエストの実行方法を完全に制御できます。この例では、このメソッドを使用することにより、証明書エラーを無視することができます。この例では、有効または無効な証明書を使用してセキュアサーバーに対してリクエストを行い、Cookieを渡し、Javaからcurlを使用して出力を取得する方法を示します。

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);
    }
  }


}
1
Brad Parks

ペイメントゲートウェイを使用してメッセージを送信するためだけにURLをヒットする場合は、次のようにWebビューを使用しました。 Android webview

視認性が失われたアクティビティでウェブビューを作成します。あなたがする必要があること:このウェブビューをロードするだけです..このように:

 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");

簡単です。

このリンクからSSLTolerentWebViewClientを取得します。 Android webview でsslを使用せずにhttps URLをロードする方法

0
Amanpreet Singh