170

大規模な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アプリケーションを設定するための推奨、標準、または最良の方法は何ですか?上記で考えているすべての目標を達成できますか、それとも妥協する必要がありますか?ファイルとディレクトリ、構成設定、およびほとんどまたはまったくコードを含まないオプションはありますか?

4

5 に答える 5

172

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

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

あなたはそれを作成しSSLSocketFactoryて、それを維持したいと思うでしょう。これは、それを初期化する方法のスケッチです:

/* 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は「キーエントリ」では機能しませんが、「信頼できるエントリ」では問題ありません)。 )。

keytool -import -file selfsigned.pem -alias server -keystore server.jks
于 2009-05-13T17:22:48.710 に答える
19

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

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

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

于 2015-12-30T16:57:41.990 に答える
14

を作成する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を解決する方法は?

于 2012-11-02T10:06:24.790 に答える
12

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

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

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

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

于 2009-05-13T17:17:23.697 に答える
12

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

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

したがって、これは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は、前述のダミーのトラストマネージャーの実装です。

于 2009-05-13T17:25:22.593 に答える