web-dev-qa-db-ja.com

Javaクライアントでサーバーの自己署名SSL証明書を受け付ける

それは標準的な質問のように見えますが、私はどこにも明確な方向性を見つけることができませんでした。

私はおそらく自己署名(または期限切れ)証明書を使ってサーバーに接続しようとしているJavaコードがあります。コードは次のエラーを報告します。

[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught 
when processing request: Sun.security.validator.ValidatorException: PKIX path 
building failed: Sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

私が理解しているように、私は keytool を使用し、そしてJavaにこの接続を許可してもよいことを伝えなければなりません。

この問題を解決するためのすべての手順は、keytoolに精通していることを前提としています。

サーバー用の秘密鍵を生成して鍵ストアにインポートする

詳細な指示を投稿できる人はいますか?

私はunixを実行しているので、bashスクリプトが最善です。

それが重要かどうかはわかりませんが、コードはjbossで実行されます。

182
Nikita Rybak

基本的に2つの選択肢があります。JVMトラストストアに自己署名証明書を追加するか、またはクライアントを次のように設定します。

オプション1

ブラウザから証明書をエクスポートし、それをJVMトラストストアにインポートします(信頼チェーンを確立するため)。

<Java_HOME>\bin\keytool -import -v -trustcacerts
-alias server-alias -file server.cer
-keystore cacerts.jks -keypass changeit
-storepass changeit 

オプション2

証明書検証を無効にします。

// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { 
    new X509TrustManager() {     
        public Java.security.cert.X509Certificate[] getAcceptedIssuers() { 
            return new X509Certificate[0];
        } 
        public void checkClientTrusted( 
            Java.security.cert.X509Certificate[] certs, String authType) {
            } 
        public void checkServerTrusted( 
            Java.security.cert.X509Certificate[] certs, String authType) {
        }
    } 
}; 

// 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 (GeneralSecurityException e) {
} 
// Now you can access an https URL without having the certificate in the truststore
try { 
    URL url = new URL("https://hostname/index.html"); 
} catch (MalformedURLException e) {
} 

オプション#2はまったくお勧めできません。トラストマネージャを無効にすると、SSLの一部が無効になり、中間者攻撃に対して脆弱になります。オプション#1を選択するか、もっと良いことには、よく知られたCAによって署名された「本物の」証明書をサーバーに使用させます。

277
Pascal Thivent

私はこの問題をJDK 8u74の時点でデフォルトのJVMトラステッドホストの一部ではない証明書プロバイダに追いかけました。プロバイダは www.identrust.com ですが、接続しようとしていたドメインではありませんでした。そのドメインはこのプロバイダから証明書を取得しています。 クロスルートはJDK/JREのデフォルトリストで信頼されますか? - を参照してください。 どのブラウザとオペレーティングシステムがLet's暗号化をサポートしているか もご覧ください。

それで、私が興味を持っていたドメインに接続するために、それはidentrust.comから発行された証明書を持っていました、私は以下のステップをしました。基本的に、私はidentrust.com(DST Root CA X3)証明書をJVMから信頼されるようにしなければなりませんでした。私はApache HttpComponents 4.5を使ってそうすることができました:

1: 証明書チェーンのダウンロード手順 で、信頼から証明書を入手します。 DST Root CA X のリンクをクリックしてください。

2:文字列を「DST Root CA X3.pem」という名前のファイルに保存します。ファイルの最初と最後に、必ず "----- BEGIN CERTIFICATE -----"と "----- END CERTIFICATE -----"の行を追加してください。

3:以下のコマンドを使用して、Javaキーストアファイルcacerts.jksを作成します。

keytool -import -v -trustcacerts -alias IdenTrust -keypass yourpassword -file dst_root_ca_x3.pem -keystore cacerts.jks -storepass yourpassword

4:作成されたcacerts.jksキーストアをJava /(maven)アプリケーションのresourcesディレクトリにコピーします。

5:このファイルをロードしてApache 4.5 HttpClientに添付するには、次のコードを使用します。 indetrust.com utilから発行された証明書を持つすべてのドメインの問題が解決されます。util OracleはJREのデフォルトキーストアに証明書を含めます。

SSLContext sslcontext = SSLContexts.custom()
        .loadTrustMaterial(new File(CalRestClient.class.getResource("/cacerts.jks").getFile()), "yourpasword".toCharArray(),
                new TrustSelfSignedStrategy())
        .build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
        .setSSLSocketFactory(sslsf)
        .build();

プロジェクトがビルドされると、cacerts.jksがクラスパスにコピーされ、そこからロードされます。現時点では、私は他のSSLサイトをテストしませんでしたが、この証明書に上記のコードが「連鎖」している場合、それらも機能しますが、やはり私にはわかりません。

参照: カスタムSSLコンテキスト and Java HttpsURLConnectionを使用して自己署名証明書を受け入れる方法は?

7
K.Nicholas

Apache HttpClient 4.5は自己署名証明書の受け入れをサポートしています。

SSLContext sslContext = SSLContexts.custom()
    .loadTrustMaterial(new TrustSelfSignedStrategy())
    .build();
SSLConnectionSocketFactory socketFactory =
    new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
    RegistryBuilder.<ConnectionSocketFactory>create()
    .register("https", socketFactory)
    .build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);        
CloseableHttpClient httpClient = HttpClients.custom()
    .setConnectionManager(cm)
    .build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);

これはTrustSelfSignedStrategyを使うSSLソケットファクトリを構築し、それをカスタムコネクションマネージャに登録してからそのコネクションマネージャを使ってHTTP GETを行います。

私は「本番でこれをしない」と唱える人たちに同意しますが、本番外で自己署名証明書を受け入れるためのユースケースがあります。私達は自動化された統合テストでそれらを使用しているので、私達は生産用ハードウェア上で走っていない時でさえ私達はSSLを使っています(生産のように)。

7
spiffy

デフォルトのソケットファクトリを設定するのではなく(IMOは悪いことです) - yhisは、あなたが開こうとしているすべてのSSL接続ではなく、現在の接続に影響します。

URLConnection connection = url.openConnection();
    // JMD - this is a better way to do it that doesn't override the default SSL factory.
    if (connection instanceof HttpsURLConnection)
    {
        HttpsURLConnection conHttps = (HttpsURLConnection) connection;
        // Set up a Trust all manager
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
        {

            public Java.security.cert.X509Certificate[] getAcceptedIssuers()
            {
                return null;
            }

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

            public void checkServerTrusted(
                Java.security.cert.X509Certificate[] certs, String authType)
            {
            }
        } };

        // Get a new SSL context
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        sc.init(null, trustAllCerts, new Java.security.SecureRandom());
        // Set our connection to use this SSL context, with the "Trust all" manager in place.
        conHttps.setSSLSocketFactory(sc.getSocketFactory());
        // Also force it to trust all hosts
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        // and set the hostname verifier.
        conHttps.setHostnameVerifier(allHostsValid);
    }
InputStream stream = connection.getInputStream();
4
Jon Daniel

すべてのSSL証明書を信頼します。 - テストサーバーでテストする場合は、SSLを回避できます。しかし、本番用にこのコードを使用しないでください。

public static class NukeSSLCerts {
protected static final String TAG = "NukeSSLCerts";

public static void nuke() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] { 
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    X509Certificate[] myTrustedAnchors = new X509Certificate[0];  
                    return myTrustedAnchors;
                }

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

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

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
    } catch (Exception e) { 
    }
}

}

この関数は、ActivityのonCreate()関数またはアプリケーションクラスで呼び出してください。

NukeSSLCerts.nuke();

これはAndroidのバレーボールに使用できます。

1
Ashish Saini

すべての証明書を信頼するより良い代替方法があります。特定の証明書を具体的に信頼するTrustStoreを作成し、これを使用してSSLContextを設定し、SSLSocketFactoryに設定するHttpsURLConnectionを作成します。完全なコードは次のとおりです。

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

HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());

または、KeyStoreをファイルから直接ロードするか、信頼できるソースからX.509証明書を取得できます。

このコードでは、cacertsの証明書は使用されないことに注意してください。この特定のHttpsURLConnectionは、この特定の証明書のみを信頼します。

0

「彼ら」が自己署名証明書を使用している場合は、サーバーを使用可能にするために必要な手順を実行するのはユーザー次第です。特にそれは信頼できる方法であなたに彼らの証明書をオフラインで提供することを意味します。それで彼らにそれをさせなさい。次に、JSSEリファレンスガイドで説明されているようにkeytoolを使用してそれをトラストストアにインポートします。ここに掲載されている安全でないTrustManagerについてさえ考えてはいけません。

EDIT17(!)の投票者、および以下に挙げる多くのコメンターのために、私がここに書いたものを実際に読んだことがない人は、これはnot自己署名証明書に対抗するためのジェレミアド。自己署名証明書に問題はありません正しく実装されている場合ただし証明書を実装する正しい方法は、証明書を配信することです安全にオフラインプロセスを介して、認証されていないチャンネル経由ではなく、認証に使用されます。確かにこれは明らかですか?何千もの支店を持つ銀行から私の会社まで、私がこれまで働いたことのあるすべてのセキュリティ対応組織にとって明らかです。信頼できるall証明書を信頼するクライアントサイドのコードベースの「解決策」、またはCAとして設定された任意の機関によって署名された自己署名証明書は、ipso facto]です。安全ではありません。それはただ安全で遊んでいます。無意味です。あなたはプライベート、改ざん防止、返信防止、注射防止の会話をしています...誰かと。だれでも。真ん中の男。なりすまし。だれでも。平文を使用することもできます。

0
user207421

受け入れられた答えは良いのですが、私はMac上でIntelliJを使用していたのでJava_HOMEパス変数を使用してそれを動作させることができなかったのでこれに何かを追加したいのですが。

IntelliJからアプリケーションを実行したときにJava Homeが異なっていたことがわかります。

それがどこにあるのかを正確に把握するためには、信頼できる証明書が読み取られる場所であるSystem.getProperty("Java.home")を実行することができます。