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


215

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

おそらく自己署名(または期限切れ)証明書を使用してサーバーに接続しようとしている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で実行されます。


2
Java HttpsURLConnectionで自己署名証明書を受け入れる方法を参照してください。明らかに、サイトに有効な証明書を使用させることができればより良いでしょう。
Matthew Flaschen

2
リンクをありがとう、私は検索中にそれを見ませんでした。しかし、どちらのソリューションにもリクエストを送信するための特別なコードが含まれており、私は既存のコードを使用しています(Java用のAmazon WSクライアント)。それぞれ、私が接続している彼らのサイトであり、証明書の問題を修正することはできません。
Nikita Rybak

2
@MatthewFlaschen- 「明らかに、サイトに有効な証明書を使用させることができれば...」 -クライアントが信頼する場合、自己署名証明書は有効な証明書です。CA /ブラウザのカルテルに信頼を与えることはセキュリティ上の欠陥であると多くの人が考えています。
jww 2017年

3
関連する、世界で最も危険なコード:非ブラウザーソフトウェアでのSSL証明書の検証を参照してください 。(検証を無効にするスパム回答を受け取っているように見えるため、リンクが提供されています)。
jww

回答:


306

ここには基本的に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によって署名された「実際の」証明書をサーバーに使用させます。


7
MIM攻撃だけではありません。間違ったサイトへの接続に対して脆弱になります。それは完全に安全ではありません。RFC 2246を参照してください。このTrustManagerは常に投稿することに反対しています。独自の仕様では正しくありません。
ローン侯爵

10
@EJP私は2番目のオプションを推奨していません(明確にするために回答を更新しました)。ただし、投稿しないと何も解決されません(これは公開情報です)。私見は反対票に値しません。
Pascal Thivent、

135
@EJP物事を隠すことではなく、人々に教えることです。したがって、物事を秘密にしたり、あいまいにしたりすることは、まったく解決策ではありません。このコードは公開されており、Java APIは公開されています。無視するよりも、それについて話す方が良いです。しかし、私はあなたが同意しないであなたと暮らすことができます。
Pascal Thivent、

10
もう1つのオプション–あなたが言及していないオプション–は、サーバーの証明書を自分で修正するか、関連するサポート担当者を呼び出すことによって修正することです。単一ホスト証明書は本当に非常に安価です。自己署名したものをいじくりまわすのは、ペニーワイズのポンドバカです(つまり、英語のイディオムに精通していない人にとっては、ほとんど何も節約するのに多くの費用がかかる、まったく愚かな優先順位のセットです)。
ドナルフェロー2011年

2
@ Rich、6年遅れますが、ロックアイコンをクリックして、Firefoxでサーバー証明書を取得することもできます->詳細情報->証明書の表示->詳細タブ->エクスポート...
シッダールタ

9

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

だから、私が興味を持ったドメインに接続するために、私から証明書が発行されましたidentrust.comが、次の手順を実行しました。基本的に、私はidentrust.com(DST Root CA X3)証明書を取得して、JVMが信頼できるようにする必要がありました。私は、Apache HttpComponents 4.5を次のように使用してそれを行うことができました:

1:証明書チェーンのダウンロード手順でindettrustから証明書を取得します。クリックしてDSTルートCAのX3のリンク。

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)アプリケーションのリソースディレクトリにコピーします。

5:次のコードを使用してこのファイルをロードし、Apache 4.5 HttpClientにアタッチします。これにより、indetrust.comutil 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コンテキストJava HttpsURLConnectionで自己署名証明書を受け入れるにはどうすればよいですか?


問題は自己署名証明書についてです。この答えは違います。
ローンの侯爵

4
質問(および回答)をもう少し詳しく読んでください。
K.Nicholas

9

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を使用します(実稼働と同様)。


6

デフォルトのソケットファクトリを設定するのではなく(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();

2
あなたはcheckServerTrusted、実際に証明書を信頼する必要なロジックを実装していない非信頼できる証明書が拒否されていることを確認します。これは良い読みになるかもしれません:世界で最も危険なコード:非ブラウザーソフトウェアでのSSL証明書の検証
jww

1
あなたのgetAcceptedIssuers()方法はspecifcationに準拠していない、そしてこの「ソリューション」は根本的に不安定なまま。
Lorneの侯爵

3

すべての証明書を信頼するより良い代替手段があります:TrustStore特定の証明書を特に信頼するを作成し、これを使用しSSLContextSSLSocketFactoryに設定するを取得するを作成します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は、この特定の証明書のみを信頼します。


1

すべての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) { 
    }
}

}

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

NukeSSLCerts.nuke();

AndroidのVolleyで使用できます。


コードが機能しないのでない限り、なぜあなたが反対票を投じられているのか完全にはわかりません。おそらく、元の質問にAndroidのタグが付けられていなかったときに、Androidが何らかの形で関与していることが前提になっているのでしょう。
Lo-Tan

1

受け入れられた答えは結構ですが、MacでIntelliJを使用していて、JAVA_HOMEパス変数を使用して機能させることができなかったので、これに何かを追加したいと思います。

IntelliJからアプリケーションを実行すると、Javaホームが異なることがわかりました。

それがどこにあるかを正確に把握するには、System.getProperty("java.home")信頼できる証明書の読み取り元と同じように行うことができます。


0

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

編集私がここに書いたものを実際に読んでいない17人のダウンボッターと以下の多数のコメンターの利益のために、これは自己署名証明書に対するジェレミアではありません正しく実装されていれば 、自己署名証明書問題はありません。ただし、それらを実装する正しい方法は、認証に使用される未認証のチャネル経由ではなく、オフラインプロセス経由で証明書を安全に配信することです。確かにこれは明らかですか?何千もの支店を持つ銀行から自社まで、私がこれまで働いてきたすべてのセキュリティ対応組織にとって、それは確かに明白です。すべてを信頼するクライアント側のコードベース「ソリューション」絶対に誰が署名した自己署名証明書を含む証明書、またはCAとしての地位を設定する任意のarbitary体であり、事実上安全ではありません。セキュリティで遊んでいるだけです。それは無意味です。誰かとの、改ざん防止、返信防止、注入防止のプライベートな会話をしています。だれでも。真ん中の男。なりすまし。だれでも。プレーンテキストを使用することもできます。


2
一部のサーバーがhttpsを使用することを決定したからといって、クライアント持つ人が自分の目的でセキュリティについてがらくたを与えるということではありません。
ガス2016

3
この回答があまりにも多くの票を集められたことに驚いています。その理由をもう少し理解したいと思います。EJPは、重大なセキュリティ上の欠陥を許容するため、オプション2を行うべきではないことを示唆しているようです。誰かが(展開前の設定以外に)なぜこの回答の品質が低いのか説明できますか?
cr1pto 2016年

2
まあ、私はそれのすべてを推測します。何をしてない「それは、措置をとることを彼らにアップしている彼らのサーバーを使用可能にするために必要な」意味ですか?サーバーオペレーターは、サーバーオペレーターを使用可能にするために何をする必要がありますか?あなたが考えているステップは何ですか?それらのリストを提供できますか?しかし、1,000フィートに戻ると、それはOPが尋ねた問題にどのように関係していますか?彼はクライアントにJavaの自己署名証明書を受け入れる方法を知りたいと思っています。これは、OPが証明書を受け入れることができるため、コードに信頼を与えるという単純な問題です。
jww

2
@EJP-私が間違っている場合は訂正してください。自己署名証明書を受け入れることは、クライアント側のポリシー決定です。サーバー側の操作とは関係ありません。クライアントが決定を下す必要があります。鍵配布の問題を解決するには、パリティーが協調して動作する必要がありますが、それはサーバーを使用可能にすることとは関係ありません。
jww

3
@EJP-確かに、「すべての証明書を信頼する」とは言いませんでした。多分私は何かが足りない(またはあなたが物事を読みすぎている)... OPには証明書があり、それは彼に受け入れられます。彼はそれを信頼する方法を知りたいです。私はおそらく髪を分割していますが、証明書をオフラインで配信する必要はありません。帯域外検証を使用する必要がありますが、これは「クライアントにオフラインで配信する必要がある」とは異なります。彼はサーバーとクライアントを製造するショップでさえあるかもしれないので、彼は必要な先験的な知識を持っています。
jww


0

RHELでは、トップコメントで提案されているようにkeytoolを使用する代わりに、新しいバージョンのRHEL 6からupdate-ca-trustを使用できます。証明書はpem形式である必要があります。その後

trust anchor <cert.pem>

/etc/pki/ca-trust/source/cert.p11-kitを編集して、「証明書カテゴリ:other-entry」を「証明書カテゴリ:権限」に変更します。(または、sedを使用してスクリプトでこれを行います。)次に、

update-ca-trust

いくつかの警告:

  • RHEL 6サーバーで「信頼」を見つけることができず、yumはそれをインストールすることを提案しませんでした。私はRHEL 7サーバーでそれを使用し、.p11-kitファイルをコピーしてしまいました。
  • これを機能させるには、次の操作が必要になる場合がありますupdate-ca-trust enable。これにより、/ etc / pki / java / cacertsが/ etc / pki / ca-trust / extracted / java / cacertsを指すシンボリックリンクに置き換えられます。(したがって、前者を最初にバックアップすることをお勧めします。)
  • Javaクライアントが他の場所に保存されているcacertsを使用している場合は、手動で/ etc / pki / ca-trust / extracted / java / cacertsへのシンボリックリンクに置き換えるか、そのファイルに置き換えます。

0

ライブラリにURLを渡していて、url.openConnection();ジョンダニエルの答えを採用したという問題がありました。

public class TrustHostUrlStreamHandler extends URLStreamHandler {

    private static final Logger LOG = LoggerFactory.getLogger(TrustHostUrlStreamHandler.class);

    @Override
    protected URLConnection openConnection(final URL url) throws IOException {

        final URLConnection urlConnection = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile()).openConnection();

        // adapated from
        // /programming/2893819/accept-servers-self-signed-ssl-certificate-in-java-client
        if (urlConnection instanceof HttpsURLConnection) {
            final HttpsURLConnection conHttps = (HttpsURLConnection) urlConnection;

            try {
                // Set up a Trust all manager
                final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

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

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

                // Get a new SSL context
                final 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
                final HostnameVerifier allHostsValid = new HostnameVerifier() {
                    @Override
                    public boolean verify(final String hostname, final SSLSession session) {
                        return true;
                    }
                };

                // and set the hostname verifier.
                conHttps.setHostnameVerifier(allHostsValid);

            } catch (final NoSuchAlgorithmException e) {
                LOG.warn("Failed to override URLConnection.", e);
            } catch (final KeyManagementException e) {
                LOG.warn("Failed to override URLConnection.", e);
            }

        } else {
            LOG.warn("Failed to override URLConnection. Incorrect type: {}", urlConnection.getClass().getName());
        }

        return urlConnection;
    }

}

このクラスを使用すると、次のコマンドで新しいURLを作成できます。

trustedUrl = new URL(new URL(originalUrl), "", new TrustHostUrlStreamHandler());
trustedUrl.openConnection();

これには、ローカライズされ、デフォルトを置き換えないという利点がありますURL.openConnection

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.