web-dev-qa-db-ja.com

HttpsURLConnectionで証明書認証を使用するにはどうすればよいですか?

HTTPS URLに接続しようとしていますが、サードパーティのソフトウェアによってシステムに配置された証明書でクライアント認証を使用する必要があります。

私はそれをどのように見つけるか使用することになっているのか少しもわかりません、そして私が続けなければならないのはC#サンプルコードだけです、それは私が持っているすべてのJava回答とは大きく異なりますこれについて見つかりました(たとえば、KeyStoreには何らかのパスワードが必要なようです)

これは私が持っているC#サンプルコードです

System.Security.Cryptography.X509Certificates.X509CertificateCollection SSC_Certs = 
    new System.Security.Cryptography.X509Certificates.X509CertificateCollection();

Microsoft.Web.Services2.Security.X509.X509CertificateStore WS2_store =
    Microsoft.Web.Services2.Security.X509.X509CertificateStore.CurrentUserStore(
    Microsoft.Web.Services2.Security.X509.X509CertificateStore.MyStore);

WS2_store.OpenRead();

Microsoft.Web.Services2.Security.X509.X509CertificateCollection WS2_store_Certs = WS2_store.Certificates;

そして、WS2_store_Certs CertificateCollectionを繰り返し処理し、それらをすべてチェックします。さらに少し進んで、次のように証明書を設定します。

HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url_string);
httpWebRequest.ClientCertificates = SSC_Certs;

証明書をどのように見つけるかわからない場合でも、これはすべてかなり論理的に見えますが、これに相当するJava)を見つけることができませんでした。

[〜#〜]更新[〜#〜]

私が作成している接続は、JDK 5に依存するより大きなアプリケーションの一部ですが、sunmscapijarを使用して探している証明書を見つけることができました。ただし、Windowsキーストアを使用して接続しようとするとエラーが発生するため、Windowsストアから必要な証明書を取得し、デフォルトのJava 1に挿入することで、問題を回避できたと思いました。 EOFExceptionに続いて、「ハンドシェイク中にリモートホストが接続を閉じました」というSSLHandshakeExceptionが発生します。必要な証明書がここの証明書チェーンに表示されているため、sslデバッグトレースですぐに問題が発生することはありません。

ClientKeyExchange全体を実行し、終了したことを示し、その直後にデバッグログから取得する最後のメッセージは次のとおりです。

[write] MD5 and SHA1 hashes:  len = 16
0000: 14 00 00 0C D3 E1 E7 3D   C2 37 2F 41 F9 38 26 CC  .......=.7/A.8&.
Padded plaintext before ENCRYPTION:  len = 32
0000: 14 00 00 0C D3 E1 E7 3D   C2 37 2F 41 F9 38 26 CC  .......=.7/A.8&.
0010: CB 10 05 A1 3D C3 13 1C   EC 39 ED 93 79 9E 4D B0  ....=....9..y.M.
AWT-EventQueue-1, WRITE: TLSv1 Handshake, length = 32
[Raw write]: length = 37
0000: 16 03 01 00 20 06 B1 D8   8F 9B 70 92 F4 AD 0D 91  .... .....p.....
0010: 25 9C 7D 3E 65 C1 8C A7   F7 DA 09 C0 84 FF F4 4A  %..>e..........J
0020: CE FD 4D 65 8D                                     ..Me.
AWT-EventQueue-1, received EOFException: error

接続を設定するために使用しているコードは

KeyStore jks = KeyStore.getInstance(KeyStore.getDefaultType());
jks.load(null, null);

KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
jks.setCertificateEntry("alias", cert1); //X509Certificate obtained from windows keystore
kmf.init(jks, new char[0]);

SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(kmf.getKeyManagers(), new TrustManager[]{tm}, null);

sslsocketfactory = sslContext.getSocketFactory();
System.setProperty("https.proxyHost", proxyurl);
System.setProperty("https.proxyPort", proxyport);

Authenticator.setDefault(new MyAuthenticator("proxyID", "proxyPassword"));
URL url = new URL(null, urlStr, new Sun.net.www.protocol.https.Handler());
HttpsURLConnection uc = (HttpsURLConnection) url.openConnection();
uc.setSSLSocketFactory(sslsocketfactory);
uc.setAllowUserInteraction(true);
uc.setRequestMethod("POST");
uc.connect();

(証明書ファイルを見つける方法がわからないため、まだHttpClientを試していません。また、これがすべてのクライアントシステムで常に同じであるかどうかもわかりません。)

別の更新

WINDOWS-ROOTキーストアで必要な証明書のCAを見つけました(そして、それらがすべてチェックアウトされていることを確認するために.verify()でチェックしました)、それらをJava =キーストアも同様ですが、まだ何も変更されていません。TrustStoreに入るはずですが、プログラムでそれを行う方法はまだ見つかりません(エンドユーザーにこの種のことを行わせたくないのですが、私が彼らから保証できるのは、この途方もなく長い質問の冒頭で述べたサードパーティのソフトウェアのために、証明書とCAが存在することだけです。)

さらに多くの更新

以前の更新に加えて、私の問題は私のCAがJavaのcacertsファイルにないという事実にあるに違いないという結論に達しました。そのため、サーバーから信頼できるCAのリストを取得しますが、それらを認識せず、その後、接続障害の原因となる単一の証明書を送り返しません。したがって、問題は残ります。Javaを取得して、キーストアをトラストストアとして使用するか、プログラムで証明書をcacertsに追加する(ファイルパスを必要としない)にはどうすればよいですか?それが不可能な場合は、秘密のオプションC、voodooを残します。念のため、デューク人形を針で刺し始めます。

13
Valyrion

エクスポート不可として設定されていたため、秘密鍵の問題であることが判明しました。これは、Windowsストアからのみ秘密鍵を取得できることを意味するため、アプリケーションの他の部分にあまり影響を与えずに必要なjdk6クラスを機能させるために、多くの混乱を伴う問題を回避して「修正」しました。

0
Valyrion

さて、あなたの質問のタイトルはHttpsURLConnectionで証明書認証を使用するにはどうすればよいですか?そのための実用的な例があります。それが機能するためには、oneの前提条件があります:

  1. 証明書ファイルが含まれているキーストアが必要です。 (あなたのシナリオではこれが正確に許可されていないことはわかっていますが、問題を少し絞り込むことができるように、ここで少しフォローしてください。少し複雑すぎてすぐに答えることができません。コウモリ。

したがって、最初に実際の証明書ファイルを入手してください。 Windows 7を使用している場合、これは次の手順で実行できます。

  1. 開くInternet Explorer
  2. 開くツール(Internet Explorer 9ではcogアイコンです)、
  3. クリックインターネットオプション
  4. Contentタブに移動し、
  5. クリック証明書
  6. 手元の証明書を見つけて、
  7. それをクリックしてExportをクリックし、ファイルに保存します(DERエンコードされたバイナリX.509)。

エクスポートした後、他の証明書の中から削除し、Javaが何らかの方法で使用しないことを確認します。それがどうかはわかりません使用できますが、痛くはありませんでした。

ここで、キーストアファイルを作成し、エクスポートされた証明書をそのファイルにインポートする必要があります。これは、次の方法で実行できます。

> keytool -importcert -file <certificate> -keystore <keystore> -alias <alias>

明らかに、keytoolは機能するためのパス上にある必要があります。これはJDKの一部です。

パスワード(キーストアのパスワード。証明書で何かを行う必要はありません)の入力を求められますが、私はしません。今すぐ""に設定する方法を知っているので、passwordなどに設定してください。

この後、次の手順で、プロキシを介してエンドポイントへの安全な接続を確立できます。

  1. まず、キーストアファイルをロードします。

    InputStream trustStream = new FileInputStream("path/to/<keystore>");
    char[] trustPassword = "<password>".toCharArray();
    
  2. KeyStoreを初期化します。

    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    trustStore.load(trustStream, trustPassword);
    
  3. TrustManagerオブジェクトを初期化します。 (これらは証明書の解決などを処理すると思いますが、私に関する限り、これは魔法です。

    TrustManagerFactory trustFactory =
        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustFactory.init(trustStore);
    TrustManager[] trustManagers = trustFactory.getTrustManagers();
    
  4. 新しいSSLContextを作成し、TrustManagerオブジェクトをロードして設定しますデフォルトとして。 SSLContext.getDefault()はクラスの変更不可能なインスタンスを返すため、注意してください(または、デフォルトのインスタンスのようにre-initialized、しかし何でも)、それがSSLContext.getInstance( "SSL")。また、この新しいSSLContextをデフォルトとして設定することを忘れないでください。これがないと、コードはpoof

    SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, trustManagers, null);
    SSLContext.setDefault(sslContext);
    
  5. プロキシを作成し、その認証を設定します。 (System.setProperty(...)を使用する代わりに、Proxyクラスを使用します。ああ、Type.HTTPに誤解されないでください。

    Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("<Host>", <port>));
    
  6. プロキシの認証を設定します。 (私は 無料のプロキシ を使用しましたが、これは認証を必要としなかったため、現在問題のその部分をテストできませんでした。

    Authenticator.setDefault(new Authenticator() {
    
      @Override
      protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication("<user>", "<password>".toCharArray());
      }
    });
    
  7. 以前に作成したプロキシを接続に渡して、エンドポイントに接続します。 (会社のサービスのURLの1つを使用しました。これは、証明書を要求します。もちろん、この証明書を自分のキーストアにインポートしました。

    URL url = new URL("<endpoint>");
    URLConnection connection = url.openConnection(proxy);
    connection.connect();
    

このように機能しない(エラーが発生する)場合は、HttpsURLConnectionで試してください。

   HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
   httpsConnection.setAllowUserInteraction(true);
   httpsConnection.setRequestMethod("POST");

基本的に、setAllowUserInteractionは、サーバー(接続先のURLが指すリソースが配置されている場所)が資格情報を要求した場合に起動します。さて、that自体をテストすることはできませんでしたが、認証を必要としないサーバーでこの赤ちゃんを動作させることができるかどうかを確認しますサーバーが自分自身を認証するように要求するので、接続がすでに確立された後にのみ、そのリソースにアクセスします。

それでもエラーが発生する場合は、投稿してください。

12

前回の試みで覚えていることから、HttpsURLConnectionは使用できません。これをサポートしているApacheHttpClientライブラリを確認できます。

これは、プロセスのアイデアを与えるコードサンプルです。

_String server = "example.com";
int port = 443;
EasySSLProtocolSocketFactory psf = new EasySSLProtocolSocketFactory();
InputStream is = readFile("/path/to/certificate");
KeyMaterial km = new KeyMaterial(is, "certpasswd".toCharArray());
easy.setKeyMaterial(km);

Protocol proto = new Protocol("https", (ProtocolSocketFactory) psf, port);
HttpClient httpclient = new HttpClient();
httpclient.getHostConfiguration().setHost(server, port, proto);
_

編集(トムのコメントに関して):

Windowsキーストアに保存されている証明書を取得する方法についての考えを次に示します。

  • Sun Cryptographyスイートを使用する必要があります(つまり、Sun Java 6 JDK)
  • 次のようにキーストアを取得できます。ks = KeyStore.getInstance("Windows-MY");
  • 次の方法でロードできます:ks.load(null, null);。 JVMはWindosキーストアをロードし、キーストアパスワードの要求を処理します。
  • その後、他のキーストアと同じようにキーストアをナビゲートできます。
0
Vivien Barousse