HTTPS / SSLを介したJavaクライアント証明書


116

私はJava 6をHttpsURLConnection使用しており、クライアント証明書を使用してリモートサーバーに対してを作成しようとしています。
サーバーは自己署名ルート証明書を使用しており、パスワードで保護されたクライアント証明書を提示する必要があります。サーバールート証明書とクライアント証明書を/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts(OSX 10.5)で見つけたデフォルトのJavaキーストアに追加しました。キーストアファイルの名前は、クライアント証明書がそこに入るはずではないことを示唆しているようです?

とにかく、このストアにルート証明書を追加すると悪名高い問題が解決しました javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

しかし、今はクライアント証明書の使い方にこだわっています。私は2つのアプローチを試しましたが、どちらも私をどこにも連れて行きません。
まず、お勧めします:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://somehost.dk:3049");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
// The last line fails, and gives:
// javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

私はHttpsURLConnectionクラスをスキップしようとしました(サーバーとHTTPをやり取りしたいので理想的ではありません)。代わりに次のようにします。

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049);
InputStream inputstream = sslsocket.getInputStream();
// do anything with the inputstream results in:
// java.net.SocketTimeoutException: Read timed out

ここでクライアント証明書が問題であるかどうかさえわかりません。


私は、キーストアとトラストストアに追加するかをニーズを識別するためにどのようにクライアントからの2つの証明書を与えられたあなたはすでに問題の似たようなものを経験してきたように、この問題を特定する助けを喜ばせることができているstackoverflow.com/questions/61374276/...
henrycharles

回答:


100

最後にそれを解決しました;)ここで強力なヒントを得ました(ガンダルフの回答にも少し触れました)。欠落しているリンクは、(ほとんどの場合)以下のパラメーターの最初のものであり、ある程度、キーストアとトラストストアの違いを見過ごしていました。

自己署名サーバー証明書をトラストストアにインポートする必要があります。

keytool -import -alias gridserver -file gridserver.crt -storepass $ PASS -keystore gridserver.keystore

これらのプロパティは(コマンドラインまたはコードで)設定する必要があります。

-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.keyStore=clientcertificate.p12
-Djavax.net.ssl.trustStore=gridserver.keystore
-Djavax.net.debug=ssl # very verbose debug
-Djavax.net.ssl.keyStorePassword=$PASS
-Djavax.net.ssl.trustStorePassword=$PASS

作業例コード:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://gridserver:3049/cgi-bin/ls.py");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);

String string = null;
while ((string = bufferedreader.readLine()) != null) {
    System.out.println("Received " + string);
}

私はlocalhost:8443 / Application_Name / getAttributesのようなURLを使用しています。/ getAttribute urlマッピングを使用するメソッドがあります。このメソッドは、要素のリストを返します。私はHttpsUrlConnectionを使用しましたが、接続応答コードは200ですが、inputStreamを使用すると属性リストが表示されず、ログインページのhtmlコンテンツが表示されます。認証を行い、コンテンツタイプをJSONに設定しました。提案してください
ディーパック

83

お勧めしませんが、SSL証明書の検証をすべて無効にすることもできます。

import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

public class SSLTool {

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

    // Ignore differences between given hostname and certificate hostname
    HostnameVerifier hv = new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) { return true; }
    };

    // Install the all-trusting trust manager
    try {
      SSLContext sc = SSLContext.getInstance("SSL");
      sc.init(null, trustAllCerts, new SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      HttpsURLConnection.setDefaultHostnameVerifier(hv);
    } catch (Exception e) {}
  }
}

72
このように証明書の検証を無効にすると、起こりうるMITM攻撃への接続が開かれることに注意してください。本番環境では使用しないでください
ブルーノ

3
ありがたいことに、コードはコンパイルされません。この「解決策」は根本的に安全ではありません。
ローンの侯爵2012

5
@ neu242、いいえ、それはあなたがそれを何のために使うかには本当に依存しません。SSL / TLSを使用する場合は、接続をMITM攻撃から保護する必要があります。それがポイントです。誰もトラフィックを変更できないことが保証できる場合はサーバー認証は必要ありませんが、ネットワークトラフィックを変更することもできない盗聴者がいる可能性があると思われる状況も非常にまれです。
ブルーノ

1
@ neu242コードスニペットをありがとう。私は実際にこれを非常に特定の目的(Webクロール)のために本番環境で使用することを考えており、質問であなたの実装について述べました(stackoverflow.com/questions/13076511/…)。時間があれば、それを見て、見逃したセキュリティリスクがあるかどうか教えてください。
2012年

1
@Brunoサーバーにpingしているだけの場合、MITM攻撃によって本当に影響を受けますか?
PhoonOne 2015

21

KeyStoreおよび/またはTrustStoreシステムのプロパティを設定しましたか?

java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456

またはコードから

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

javax.net.ssl.trustStoreと同じ


13

Axisフレームワークを使用してWebサービス呼び出しを処理している場合、はるかに簡単な答えがあります。クライアントがSSL Webサービスを呼び出してSSL証明書エラーを無視できるようにしたい場合は、Webサービスを呼び出す前に次のステートメントを記述します。

System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");

これが本番環境で行うには非常に悪いことであることについての通常の免責事項が適用されます。

これはAxis wikiで見つけました。


OPはAxisではなくHttpsURLConnectionを処理しています
neu242

2
わかります。私の答えが一般的なケースでより良いことを意味するつもりはありませんでした。それはあなたがいる場合だけのことですされている Axisフレームワークを使用して、あなたはそのコンテキスト内のOPの質問を有することができます。(それが私が最初にこの質問を見つけた方法です。)その場合、私が提供した方法はより単純です。
Mark Meuer 2012年

5

私にとって、これはApache HttpComponents〜HttpClient 4.xを使用して機能したものです:

    KeyStore keyStore  = KeyStore.getInstance("PKCS12");
    FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12"));
    try {
        keyStore.load(instream, "helloworld".toCharArray());
    } finally {
        instream.close();
    }

    // Trust own CA and all self-signed certs
    SSLContext sslcontext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, "helloworld".toCharArray())
        //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store
        .build();
    // Allow TLSv1 protocol only
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO
    CloseableHttpClient httpclient = HttpClients.custom()
        .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO
        .setSSLSocketFactory(sslsf)
        .build();
    try {

        HttpGet httpget = new HttpGet("https://localhost:8443/secure/index");

        System.out.println("executing request" + httpget.getRequestLine());

        CloseableHttpResponse response = httpclient.execute(httpget);
        try {
            HttpEntity entity = response.getEntity();

            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            if (entity != null) {
                System.out.println("Response content length: " + entity.getContentLength());
            }
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
    } finally {
        httpclient.close();
    }

P12ファイルには、BouncyCastleで作成されたクライアント証明書とクライアント秘密鍵が含まれています。

public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile,
    final String password)
    throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException,
    NoSuchProviderException
{
    // Get the private key
    FileReader reader = new FileReader(keyFile);

    PEMParser pem = new PEMParser(reader);
    PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject());
    JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC");
    KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);

    PrivateKey key = keyPair.getPrivate();

    pem.close();
    reader.close();

    // Get the certificate
    reader = new FileReader(cerFile);
    pem = new PEMParser(reader);

    X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject();
    java.security.cert.Certificate x509Certificate =
        new JcaX509CertificateConverter().setProvider("BC")
            .getCertificate(certHolder);

    pem.close();
    reader.close();

    // Put them into a PKCS12 keystore and write it to a byte[]
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
    ks.load(null);
    ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(),
        new java.security.cert.Certificate[]{x509Certificate});
    ks.store(bos, password.toCharArray());
    bos.close();
    return bos.toByteArray();
}

keyStoreは、秘密鍵と証明書が含まれています。
EpicPandaForce 2014年

1
convertPEMtoP12コードを機能させるには、次の2つの依存関係を含める必要があります:<dependency> <groupId> org.bouncycastle </ groupId> <artifactId> bcprov-jdk15on </ artifactId> <version> 1.53 </ version> </ dependency> < dependency> <groupId> org.bouncycastle </ groupId> <artifactId> bcpkix-jdk15on </ artifactId> <version> 1.53 </ version> </ dependency>
BirdOfPrey

@EpicPandaForceエラーが発生する:キャッチ:org.codehaus.groovy.runtime.typehandling.GroovyCastException:クラス 'org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject'のオブジェクトをクラスksのクラス 'int'にキャストできませんsetKeyEntry-何が問題なのかを示す手掛かり
Vishal Biyani 2016

ええ、厳密に型付けされた言語の代わりにGroovyを使用しています。(技術的には、このメソッドは証明書だけでなくIDと証明書を
受け取り

4

私は現在のプロジェクトでこれを行うためにApache commons HTTP Clientパッケージを使用しており、SSLと自己署名証明書(前述のようにcacertsにインストールした後)で正常に動作します。こちらをご覧ください。

http://hc.apache.org/httpclient-3.x/tutorial.html

http://hc.apache.org/httpclient-3.x/sslguide.html


1
これはかなりきちんとしたパッケージのようですが、「AuthSSLProtocolSocketFactory」をすべて機能させるクラスは、4.0beta(リリースノートに記載されているにもかかわらず)、または3.1でも、公式ディストリビューションには含まれていません。私は少しハッキングしてきましたが、接続が切断される前に5分間ハングすることで永久に動かなくなったようです。それは本当に奇妙です-私がCAとクライアント証明書を任意のブラウザにロードすると、それはただ飛んでしまいます。
1

1
Apache HTTPクライアント4はSSLContext直接を取ることができるため、を使用する代わりに、この方法ですべてを構成できますAuthSSLProtocolSocketFactory
Bruno

1
外部のキーストアを使用する代わりに、すべてのクライアント証明書をメモリ内で実行する方法はありますか?
Sridhar Sarnobat

4

サーバー証明書に問題があると思いますが、これは有効な証明書ではありません(この場合、これが「handshake_failure」の意味です):

サーバー証明書をクライアントのJREのtrustcacertsキーストアにインポートします。これはkeytoolで簡単に行えます:

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts

クリーンアップしてやり直してみたところ、ハンドシェイクの失敗はなくなりました。今、私は接続が終了する前に5分間の無音を取得します:o
Jan

1

以下のコードを使用する

-Djavax.net.ssl.keyStoreType=pkcs12

または

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

まったく必要ありません。また、独自のカスタムSSLファクトリを作成する必要もありません。

同じ問題が発生しました。私の場合、完全な証明書チェーンがトラストストアにインポートされないという問題がありました。roottoolの証明書からkeytoolユーティリティを使用して証明書をインポートします。また、cacertsファイルをメモ帳で開いて、完全な証明書チェーンがインポートされているかどうかを確認できます。証明書のインポート時に指定したエイリアス名と照合し、証明書を開いていくつ含まれているかを確認します。cacertsファイルには同じ数の証明書が含まれている必要があります。

また、アプリケーションを実行しているサーバーでcacertsファイルを構成する必要があります。2つのサーバーは、公開鍵/秘密鍵を使用して相互に認証します。


独自のカスタムSSLファクトリを作成することは、2つのシステムプロパティを設定するよりもはるかに複雑でエラーが発生しやすくなります。
ローン侯爵
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.