2

私はいくつかの関連する質問を読みましたが、それらのどれも本当に私を助けません。それでは、少し背景を説明しましょう。私は、登録、投票カードの交換、および投票を管理するサーバーが2つある投票システムを作成しています。登録ユーザーが個人データを送信する間、サーバーはデータがデータベース内のデータと一致するかどうかを確認し、ユーザーの証明書を受け入れて、次のようにトラストストアに追加します。

KeyStore.Entry entry = new KeyStore.TrustedCertificateEntry(cert);
trustKeyStore.setEntry(alias, entry, null);

次に、trustStoreをファイルに保存します。登録中に多くのユーザーが証明書を登録してから、クライアントとサーバーの両方の認証を要求する別のサーバーに接続すると、ここで問題が発生します。この「他のサーバー」は前述のtrustStoreを使用してユーザーとSSLHandshakeを実行できると思いましたが、trustStoreは「最後に登録したユーザーのみを記憶しているようです」。私は次のようにSSLServerSocketを作成しています:

    tmf.init(trustKeyStore);
    kmf.init(keyStore, password);

    // create, init SSLContext
    SSLContext sslCtx = SSLContext.getInstance("TLS");
    sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

    // create SSLSocketFactory and SSLSocket
    SSLServerSocketFactory sslSocketFactory =   (SSLServerSocketFactory)sslCtx.getServerSocketFactory();
    sslSocket = (SSLServerSocket)sslSocketFactory.createServerSocket(1234);

前に述べたように、両側認証は正常に機能しますが、trustStoreの最後のエントリに対してのみ機能します。それで、最後に私の質問は-ユーザーごとに個別のtrustStoreを作成する必要がありますか?TrustManagerをオーバーロードする必要がありますか?基本的に、SSLContext / SSLEngineですべての信頼できる証明書を反復処理し、一致する証明書が見つかった場合にハンドシェイクを実行する方法はありますか?

アップデート

いくつか明確にする必要があると思います。まず第一に、これはWebアプリケーションではなく、通常のクライアントサーバーJavaSwingアプリです。使用されるすべての証明書(サーバーまたはクライアント)は自己署名されていますが、サーバーの証明書はクライアントアプリケーションに組み込まれているため、クライアントがサーバーに接続すると、証明書を比較できます(機能します)。ブルーノが提案したのとまったく同じように解決しようとしました。ユーザーが登録した後、サーバーはデータが有効かどうかを確認し、有効な場合はクライアントに新しい証明書を発行し、トラストストアに追加してクライアントに送信します。しかし、それは最後に登録されたクライアントでも機能しませんでした。

4

2 に答える 2

3

私は問題を解決しました。よくあることですが、私の側でばかげた間違いがありました。サーバーの 1 つがキーストア全体を上書きしていました。私はこの SSL 通信をセットアップする方法を考えるのに多くの時間を費やしましたが、ウェブ上でそれについてあまり見つけられなかったので、これが将来誰かに役立つことを願っています.

サーバーのみの通信をセットアップするには、次の手順を実行する必要があります。

        KeyStore clientTrustedKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        clientTrustedKeyStore.load(null, "password".toCharArray());
        KeyStore.Entry entry = new KeyStore.TrustedCertificateEntry(cert);
        clientTrustedKeyStore.setEntry("alias", entry, null);
  1. クライアント側またはサーバー側で sslsockets を作成する際に、SSLContext に keyStore (サーバー) または trustStore (クライアント) を使用して SSLContext を「フィード」する必要があります。

    tmf = TrustManagerFactory.getInstance("SunX509");
    kmf = KeyManagerFactory.getInstance("SunX509");
    tmf.init(trustKeyStore);
    kmf.init(keyStore, password);
    
    // create, init SSLContext
        SSLContext sslCtx = SSLContext.getInstance("TLS");
        sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    

次に、sslsocketfactory とソケットを作成します。

  1. ソケットを作成した後 (サーバー側でのみ有用)、認証モードを設定します。

    sslSocket.setNeedClientAuth(ブール値);

両側検証を設定する場合、変更する必要があるのは、認証モード (明らかに)、クライアント側とサーバー側の両方のトラストストアに証明書を追加することです。

両側検証のもう 1 つの問題は、以下のシナリオが機能しないことです (ただし、少なくとも私にとっては論理的に思えます)。 1. サーバーが独自の自己署名証明書を発行し、それを trustStore に追加してから送信します。今後の接続時に認証するためにクライアントに送信します。うまくいかないのはなぜですか?クライアントが証明書を取得すると、次のようにのみキーストアに追加できるためです。

ks.setCertificateEntry(alias,cert);

SSLHandshakeに関してはこれはうまくいきません.setEntryでキーストアに追加された証明書で自分自身を認証する必要があります:

ks.setKeyEntry(alias,keyPair.getPrivate(),keyStorePassword,certChain);

どこで certChain はつまり

Certificate[] certChain = {myCert};

これですべてです。一部の人にとっては、これらすべてが非常に明白であると確信していますが、私のようなSSL初心者の助けになることを願っています:)

于 2012-02-01T12:15:44.490 に答える
3

(質問の変更後に編集されました。)

方法 1:

これを解決する 1 つの方法は、おそらく独自の CA を使用することです。

方法 2:

ハンドシェイク レベルで任意のクライアント証明書を受け入れます。クライアントの観点からサーバー証明書を受け入れることは悪い考えですが、MITM 攻撃の可能性が生じるため (たとえば、ここここを参照)、ハンドシェイク中にクライアント証明書を受け入れても同じ問題は発生しません。

ハンドシェイクの最後に、サーバー証明書がまだ検証されていれば、次のことがわかります。

  • クライアントとサーバー間の通信は、クライアント証明書認証のない HTTPS の場合と同様に、安全に確立されます。
  • クライアントは、ハンドシェーク中に提示した証明書の秘密鍵を持っています。CertificateVerifyこれは、クライアントの秘密鍵を使用して、サーバー証明書とクライアント証明書を含む、これまでのハンドシェイク中に交換されたすべてのメッセージの連結に署名するTLS メッセージによって保証されます。これは、クライアント証明書自体の (信頼) 検証に実際には依存せず、クライアント証明書の公開鍵が TLSCertificateVerifyメッセージの署名を検証できる場合にのみ機能します。

これにより、サーバーが取得したクライアント証明書が、一致する秘密鍵を持つ誰かによって使用されていることがわかります。わからないのは、その公開鍵証明書がに属しているか、つまり、相手側のユーザーが誰であるかです。通常、CA を使用して実行される検証手順が欠けているためです。証明書を発行する前にバンド機構。彼らは自己署名証明書を使用しているため、おそらく登録中に何らかの情報を介して、この帯域外の手順を実行する必要があります。

これを行うと、ユーザーと、ユーザーが証明書をより簡単に使用する方法を管理できるようになります。サーバーに共通のデータベースに現在のユーザーの証明書を保存および/または検索するだけです。アプリケーション内の を参照して、証明書にアクセスできSSLSessionます。によって返される配列の位置 0 にユーザー証明書がありますgetPeerCertificates()。次に、アプリケーション内での認証 (および承認) のために、表示された資格情報 (双方向ハンドシェイクが成功したため) を以前に表示された資格情報と単純に比較できます。

追加し続ける自己署名証明書を受け入れた場合でも、サブジェクト DN (2 つのユーザーは、意図的かどうかにかかわらず、同じものを使用することを選択できます)。

これを実装するには、メソッドで例外をスローしないSSLContextを使用する でサーバーを構成する必要があります。(他の目的で同じものを使用している場合は、デフォルトの PKIX を取得し、呼び出しを委任します。) これらの自己署名証明書のサブジェクト/発行者 DN がどうなるかはおそらくわからないため、そのためには、空の配列を返すことによって、受け入れられた CA (*) の空のリストを送信する必要があります(こちらの例を参照してください)。X509TrustManagercheckClientTrustedSSLContextTrustManagercheckServerTrustedgetAcceptedIssuers()


(*) TLS 1.0 はこの件について沈黙していますが、TLS 1.1 は明示的に空のリストを許可します (動作が規定されていません)。実際には、SSLv3/TLSv1.0 であっても、ほとんどのブラウザーで動作します。この場合、ブラウザーは選択する証明書の完全なリストを表示します (または、自動的に選択するように構成されている場合は、最初に見つかったものを選択します)。


(ここでは、HTTP のより具体的な内容を残しています... 少し文脈から外れていますが、これは他の人にとって興味深いかもしれません。)

方法 1:

最初の登録の一部として、証明書の発行を統合できます。ユーザーが詳細を登録すると、登録サーバーによってブラウザに発行される証明書も申請します。(これに慣れていない場合は、Windows のバージョンに応じて、ブラウザ内で<keygen />CRMF (Firefox の場合)、または IE の ActiveX コントロールを使用して証明書を発行する方法があります。)

方法 2:

javax.servlet.request.X509Certificateサーブレット環境では、サーブレットのリクエスト属性で取得できます

さらに、クライアント証明書が拒否されても接続が終了するとは限らないため、これによりユーザー エクスペリエンスが向上する傾向があります。Web ページを提供することはできます (おそらく HTTP 401 ステータスですが、技術的には、チャレンジ メカニズムを伴う必要があり、証明書用のメカニズムはありません) 少なくともユーザーに何か問題があることを伝えます。ハンドシェイクの失敗 (問題が発生した場合にハンドシェイク内のクライアント証明書の検証によって引き起こされる) は、証明書についてよく知らないユーザーにとって非常に混乱を招く可能性があります。欠点は、ほとんどのブラウザーでユーザー インターフェイスがサポートされていないため、(HTTP 基本認証と同様に) "ログアウト" するのが非常に難しいことです。

実装については、Apache Tomcat を使用している場合、Apache Tomcat 6.0.33 以降を使用している場合は、このSSLImplementation回答またはこの回答に興味があるかもしれません。

于 2012-02-01T01:18:59.447 に答える