web-dev-qa-db-ja.com

特定の接続で異なる証明書を使用するにはどうすればよいですか?

大規模なJavaアプリケーションに追加するモジュールは、別の会社のSSLで保護されたWebサイトと対話する必要があります。問題は、サイトが自己署名証明書を使用していることです。中間者攻撃を受けていないことを確認する証明書のコピーがあり、サーバーへの接続が成功するようにこの証明書をコードに組み込む必要があります。

基本的なコードは次のとおりです。

void sendRequest(String dataPacket) {
  String urlStr = "https://Host.example.com/";
  URL url = new URL(urlStr);
  HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  conn.setMethod("POST");
  conn.setRequestProperty("Content-Length", data.length());
  conn.setDoOutput(true);
  OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
  o.write(data);
  o.flush();
}

自己署名証明書の追加の処理が行われないと、これはconn.getOutputStream()で終了しますが、次の例外があります。

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
....
Caused by: Sun.security.validator.ValidatorException: PKIX path building failed: Sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: Sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

理想的には、私のコードはJavaに、この1つの自己署名証明書を受け入れるように、アプリケーション内のこの1つの場所で、他のどこでも受け入れないように教える必要があります。

JREの認証局ストアに証明書をインポートでき、Javaがそれを受け入れることができることを知っています。私が助けられるなら、それは私が取りたいアプローチではありません。お客様が使用していない可能性のある1つのモジュールについて、お客様のすべてのマシンで行うことは非常に侵襲的と思われます。同じJREを使用する他のすべてのJavaアプリケーションに影響し、このサイトにアクセスする他のJavaアプリケーションのオッズがゼロであっても、私はそれが気に入らないでしょう。また、簡単な操作ではありません。UNIXでは、この方法でJREを変更するためにアクセス権を取得する必要があります。

また、カスタムチェックを行うTrustManagerインスタンスを作成できることも確認しました。この1つの証明書を除くすべてのインスタンスで、実際のTrustManagerに委任するTrustManagerを作成できる可能性もあります。しかし、TrustManagerがグローバルにインストールされるように見え、アプリケーションからの他のすべての接続に影響を与えると思われます。

自己署名証明書を受け入れるようにJavaアプリケーションをセットアップするための優先、標準、または最良の方法は何ですか?上記のすべての目標を達成できますか、それとも妥協する必要がありますか?ファイルとディレクトリ、構成設定、およびほとんどないコードを含むオプションはありますか?

155
skiphoppy

SSLSocketファクトリを自分で作成し、接続する前にHttpsURLConnectionに設定します。

...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...

SSLSocketFactoryを1つ作成して、それを保持する必要があります。これを初期化する方法のスケッチを次に示します。

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
  TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();

キーストアの作成にサポートが必要な場合は、コメントしてください。


キーストアをロードする例を次に示します。

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

PEM形式の証明書を使用してキーストアを作成するには、CertificateFactoryを使用して独自のコードを記述するか、JDKからkeytoolを使用してインポートします(keytool wo n't work 「キーエントリ」の場合ですが、「信頼できるエントリ」の場合は問題ありません)。

keytool -import -file selfsigned.pem -alias server -keystore server.jks
158
erickson

この問題を解決するために、私はたくさんの場所をオンラインで読みました。これは私がそれを機能させるために書いたコードです:

                ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
                String alias = "alias";//cert.getSubjectX500Principal().getName();

                KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
                trustStore.load(null);
                trustStore.setCertificateEntry(alias, cert);
                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
                kmf.init(trustStore, null);
                KeyManager[] keyManagers = kmf.getKeyManagers();

                TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
                tmf.init(trustStore);
                TrustManager[] trustManagers = tmf.getTrustManagers();

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(keyManagers, trustManagers, null);
                URL url = new URL(someURL);
                conn = (HttpsURLConnection) url.openConnection();
                conn.setSSLSocketFactory(sslContext.getSocketFactory());

app.certificateStringは、証明書を含む文字列です。次に例を示します。

            static public String certificateString=
            "-----BEGIN CERTIFICATE-----\n" +
            "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
            "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
            ... a bunch of characters...
            "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
            "-----END CERTIFICATE-----";

上記の正確な構造を保持している限り、自己署名されている場合、証明書文字列に任意の文字を含めることができることをテストしました。ラップトップのターミナルコマンドラインで証明書文字列を取得しました。

15
Josh

SSLSocketFactoryの作成がオプションではない場合、キーをJVMにインポートするだけです

  1. 公開鍵:$openssl s_client -connect dev-server:443を取得してから、次のようなファイルdev-server.pemを作成します

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
    
  2. キーをインポートします:#keytool -import -alias dev-server -keystore $Java_HOME/jre/lib/security/cacerts -file dev-server.pem。パスワード:changeit

  3. JVMを再起動する

ソース: javax.net.ssl.SSLHandshakeExceptionの解決方法

13
user454322

JREのトラストストアをコピーし、カスタム証明書をそのトラストストアに追加してから、システムプロパティでカスタムトラストストアを使用するようにアプリケーションに指示します。このように、デフォルトのJREトラストストアはそのままにします。

欠点は、JREを更新しても、新しいトラストストアがカスタムのトラストストアに自動的にマージされないことです。

おそらく、トラストストア/ jdkを検証し、不一致をチェックするか、トラストストアを自動的に更新するインストーラーまたはスタートアップルーチンを用意することで、このシナリオを処理できます。アプリケーションの実行中にトラストストアを更新するとどうなるかわかりません。

このソリューションは、100%エレガントでも完全でもありませんが、シンプルで機能し、コードを必要としません。

Commons-httpclientを使用して自己署名証明書で内部httpsサーバーにアクセスする場合、このようなことをしなければなりませんでした。はい、私たちの解決策は、すべてを単に渡す(デバッグメッセージを記録する)カスタムTrustManagerを作成することでした。

これは、ローカルSSLContextからSSLソケットを作成する独自のSSLSocketFactoryを持つことになります。これは、ローカルTrustManagerのみが関連付けられているように設定されています。キーストア/証明書ストアの近くに行く必要はまったくありません。

したがって、これはLocalSSLSocketFactoryにあります。

static {
    try {
        SSL_CONTEXT = SSLContext.getInstance("SSL");
        SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null);
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Unable to initialise SSL context", e);
    } catch (KeyManagementException e) {
        throw new RuntimeException("Unable to initialise SSL context", e);
    }
}

public Socket createSocket(String Host, int port) throws IOException, UnknownHostException {
    LOG.trace("createSocket(Host => {}, port => {})", new Object[] { Host, new Integer(port) });

    return SSL_CONTEXT.getSocketFactory().createSocket(Host, port);
}

SecureProtocolSocketFactoryを実装する他のメソッドとともに。 LocalSSLTrustManagerは、前述のダミーのトラストマネージャーの実装です。

12
araqnid