SSLServerSocket.setWantClientAuthについて:クライアントが証明書を送信しないことを選択した
場合にこれが設定されている場合、ネゴシエーションは続行されます。
また、クライアントが証明書を送信したがトラストストアの一部ではない場合にもこれが発生することに気付きました。この場合もネゴシエーションは失敗しません。true
では、この設定の使用例は何ですか?
(いくつかのコメントに続いて、複数の編集。)
setWantClientAuth
クライアント証明書認証を要求するために使用されますが、認証が提供されない場合は接続を維持します。setNeedClientAuth
クライアント証明書の認証を要求および要求するために使用されます。適切なクライアント証明書が提示されない場合、接続は終了します。
このトピックの詳細については、TLS 仕様のクライアント証明書セクションを参照してください。ちょっとした歴史のために:
バージョン 1.2 では、「適切な証明書が利用できない場合、クライアントは証明書を含まない証明書メッセージを送信する必要があります。」. それ以前は単なる「SHOULD」でしたが、この場合、Sun JSSE クライアントはとにかく空のリストを送信します。
バージョン 1.2 では以下も追加されました。
> Also, if some aspect of the certificate chain was unacceptable (e.g.,
> it was not signed by a known, trusted CA), the server MAY at its
> discretion either continue the handshake (considering the client
> unauthenticated) or send a fatal alert.
This gives some flexibility regarding what to do when a unacceptable certificate is sent. The JSSE chooses to send a fatal alert. (`setWantAuth` could in principle carry on with invalid certificates, but not treat the peer as authenticated, as if no client certificate was sent, but this isn't the case.)
Previous versions of the TLS spec said "*If client authentication is required by the server for the handshake to continue, it may respond with a fatal handshake failure alert.*". This is the difference between need or want as implemented in the JSSE: using "need", the server responds with a fatal handshake failure, whereas using "want", the server carries on with the connection, but doesn't treat it as authenticated.
最初は、「必要」を使用しているときにクライアントが証明書を送信していなかったと思いました。実際、ほとんどのクライアントは、要求中にサーバーによって送信される CA 名にリストされている発行者のいずれかによって発行されたクライアント証明書が見つからない場合 (またはクライアントがクライアント証明書を送信できない場合)、クライアント証明書をまったく送信しません。チェーン自体を構築します。これは一般的な問題です)。デフォルトでは、JSSE はトラストストア内の CA を使用してそのリストを作成します。このため、適切な発行者がサーバーのトラストストアにない場合、クライアントはおそらくクライアント証明書をまったく送信しません。
Wireshark を使用して、クライアント証明書が送信されているかどうかを確認できます。SSL/TLS で通常使用されるポートで実行していない場合は、パケットを右クリックして、[Decode As... -> Transport -> SSL] を選択する必要があります。
そこにCertificate Request
、サーバーからのメッセージが表示されます。(何らかの理由で、Wireshark でデフォルトの JRE トラストストアを使用している場合、そのメッセージは「サーバー キー交換」メッセージの直後に「暗号化されたハンドシェイク メッセージ」として表示されます。ただし、暗号化されていません。数字をはっきりと確認できます。下部パネルのパケットの ASCII レンダリングを見ると、CA 名のリストが表示されます. おそらく、これはこのメッセージが長すぎるためだと思います.) 、Wireshark でこれをCertificate Request
メッセージとして適切にデコードすると、「識別名」セクションに受け入れられた CA のリストが表示されます。
クライアントからのメッセージも表示されるはずCertificate
です (もちろん、サーバーからのメッセージではありません)。サーバーが証明書を要求した場合 (要求または必要に応じて)、クライアントから常にこのメッセージが表示されます。
テスト CA (その CA によって発行されたクライアント証明書を使用) にアクセスできると仮定すると、次の実験を試すことができます。
そのテスト CA 証明書を使用してトラスト ストアを設定するsetWantClientAuth(true)
と、クライアントはクライアント証明書を送信し、接続が続行されます。その後、サーバーは期待どおり からクライアント証明書を取得できますSSLSession
。
デフォルトのトラスト ストア (テスト CA 証明書を含まない) を使用する場合は、 を使用しますsetWantClientAuth(true)
。CA DN はCertificate Request
. クライアントはCertificate
メッセージを送信しますが、証明書リストは空になります ( Certificates Length: 0
Wireshark 内)。ここでは、キーストアが送信するように構成されていても、適切な一致が見つからないという理由だけで、クライアントは実際にはクライアント証明書を送信していません。接続は続行されます (サーバー上の からピア証明書を読み取ろうとすると例外が発生する場合がSSLSession
ありますが、これは致命的ではありません)。これは ; の使用例ですsetWantClientAuth(true)
。setNeedClientAuth(true)
すぐに接続を終了していたでしょう。
この実験のために、Java でサーバーから送信された DN のリストを偽造することができます。
KeyManagerFactory kmf = //... Initialise a KMF with your server's keystore
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null); // Use the default trust store
TrustManager[] trustManagers = tmf.getTrustManagers();
final X509TrustManager origTrustManager = (X509TrustManager) trustManagers[0];
final X509Certificate caCert = // Load your test CA certificate here.
X509TrustManager fakeTrustManager = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// Key the behaviour of the default trust manager.
origTrustManager.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// Key the behaviour of the default trust manager.
origTrustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers() {
// This is only used for sending the list of acceptable CA DNs.
return new X509Certificate[] { caCert };
}
};
trustManagers = new X509TrustManager[] { fakeTrustManager };
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
この場合、Certificate Request
サーバーから送信されるメッセージには、テスト CA の DN が含まれている必要があります。ただし、その CA は実際には信頼マネージャーによって信頼されていないため、引き続き既定値が使用されます。
クライアントは証明書を送信しますが、サーバーは「javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX パスの検証に失敗しました」と言って拒否し、これにより接続が終了します。これは少なくとも、PKIX または SunX509 トラスト マネージャーを使用した SunJSSE プロバイダーを使用した実装です。これは、トラスト マネージャーの JSSE 仕様とも一致しています。「TrustManager の主な責任は、提示された認証資格情報を信頼する必要があるかどうかを判断することです。資格情報が信頼されない場合、接続は終了します。」
ここで重要な点は、 からクライアント証明書を取得できる立場にある場合SSLSession
、その証明書はトラスト マネージャーによって認証されている必要があるということSSLSession
です (つまり、ハンドシェイクが完了すると取得される証明書ではSSLSocket.getSession()
なく、getHandshakeSession()
Java 7 で導入されたを使用してハンドシェイク中に取得します)。
別のJSSEプロバイダーを使用していること、およびCA証明書がサーバーの信頼ストアにあったかどうかに関係なく、クライアントがクライアント証明書を送信していたことをコメントで示しているようです。信頼に別の異なるCA証明書もあったためです同じサブジェクト DN で保存します。これらの 2 つの CA 証明書が異なるキーを持っていると仮定すると (そうでなければ、実質的に同じ CA になります)、これはかなり深刻なバグになります: クライアント証明書認証を使用するアプリケーションは、クライアント証明書がトラスト マネージャーによって検証されていることを期待する資格があります ( JSSE リファレンス ガイドで指定されているように)。Apache Tomcat を使用している場合、クライアント証明書を取得すると、リモート接続はこの証明書で認証されたと見なされます。その時点でサーブレットがクライアント証明書を使用できる場合、
クライアントの資格情報が送信された場合はそれを知りたいが、そうでない場合は通信を停止したくない場合に使用されます。送信しないとハンドシェイクが失敗する「needClientAuth」とは対照的です。