技術者ブログ
クラウド型WAF「Scutum(スキュータム)」の開発者/エンジニアによるブログです。
金床“Kanatoko”をはじめとする株式会社ビットフォレストの技術チームが、“WAFを支える技術”をテーマに幅広く、不定期に更新中!

Java1.7のSSLサーバでRC4を優先する方法
はじめに
以前このブログにおいて『Tomcatなど、Java製のサーバでBEAST対策を行う方法』というエントリを書きました。このエントリでは『JavaではSSL通信時に、基本的にサーバ側ではCipherSuiteの優先順位を決定することができず、クライアントが送ってくるCipherSuiteのリストが優先される』ことを示しました。また、この制限を回避するためのJSSEライブラリのハックについて紹介しました。
しかしこの方法はJava1.6系を前提としたもので、Java1.7では同じ方法を採ることができません。今回は、Java1.7系で使える手法をご紹介します。
jsse.jarを動かせなくなった
Java1.6ではjsse.jarのみをJRE配下のディレクトリから取り出し、別の場所(/usr/local/jsse/lib/)に置くという方法を採りました。JavaアプリケーションサーバのTomcatはJSSE_HOMEという環境変数を設定すると実行時に自動的に使用する暗号関連のライブラリを切り替えてくれるという機能を持っており、この方法と相性が良かったのですが、Java1.7では事情が変わってしまいました。jsse.jarに含まれていたsun.security.provider.SunEntriesというクラスがrt.jar内に格納されるように構成の変更が行われてしまったらしく、JSSE_HOMEを設定しつつjsse.jarを外部に移動させるという方法が採れなくなってしまったのです。
そのため、動的にJavaバイトコードの書き換えを行うJavassistも使いにくい状態となってしまいました。そこで今回はjsse.jarに含まれるクラスファイルを書き換え、書き換えた後のクラスファイルを再びjsse.jarに埋め込んでおくという手法を採ることにします。
ServerHandshakerを書き換える
書き換える必要があるクラスはJava1.6のときと同様にServerHandshakerクラスです(ただし1.6のときとは含まれるパッケージ名が異なり、com.sun.net.ssl.internal.ssl.ServerHandshakerからsun.security.sslに変更されています)。また、書き換える場所も変わらず、chooseCipherSuite()関数を書き換えます。
ソースコードはJava.netから入手可能です。chooseCipherSuite()関数は以下のようになっており、1.6のときと若干呼び出す関数が変わっています(isEnabledからisNegotiable)。しかし基本的な構造は同じで、クライアント側が送ってきたCipherSuiteのリストに対してループしていることがわかります。
/*
* Choose cipher suite from among those supported by client. Sets
* the cipherSuite and keyExchange variables.
*/
private void chooseCipherSuite(ClientHello mesg) throws IOException {
for (CipherSuite suite : mesg.getCipherSuites().collection()) {
if (isNegotiable(suite) == false) {
continue;
}
if (doClientAuth == SSLEngineImpl.clauth_required) {
if ((suite.keyExchange == K_DH_ANON) ||
(suite.keyExchange == K_ECDH_ANON)) {
continue;
}
}
if (trySetCipherSuite(suite) == false) {
continue;
}
return;
}
fatalSE(Alerts.alert_handshake_failure,
"no cipher suites in common");
}
サーバ側のCipherSuiteの一覧を取得するために、Java1.6では親クラスのHandshakerで定義されているenabledCipherSuitesにアクセスすることができましたが、Java1.7ではactiveCipherSuitesというメンバ変数をgetActiveCipherSuites()によって取得することになります。
書き換えは以下のように行います。Java1.6のときと殆ど同じで、サーバ側のリストについてループを回すだけの簡単な変更です。
private void chooseCipherSuite(ClientHello mesg) throws IOException {
Iterator p = getActiveCipherSuites().iterator();
while( p.hasNext() )
{
CipherSuite suite = p.next();
if( !mesg.getCipherSuites().contains( suite )
|| !isNegotiable( suite )
)
{
continue;
}
if (doClientAuth == SSLEngineImpl.clauth_required)
{
if ((suite.keyExchange.toString().equals( "DH_anon" ) ) || (suite.keyExchange.toString().equals( "ECDH_anon" ) ))
{
continue;
}
}
if (trySetCipherSuite(suite) == false)
{
continue;
}
return;
}
fatalSE( sun.security.ssl.Alerts.alert_handshake_failure, "no cipher suites in common" );
}
Java内でのCipherSuiteの優先順位の変更
Java1.6では、前項のようにServerHandshakerクラスの書き換えを行えば、RC4がAESより優先されるようになりました。しかしJava1.7ではJava内部のCipherSuiteのリスト内で優先順位が変更されており、AESのような、より強固なCipherSuiteが選択されやすくなっています。そのため、ServerHandshakerクラスを書き換えただけでは、RC4が優先して使われるようにはなりません。
どのCipherSuiteが優先されるかは、CipherSuiteクラス内で定義されています。Java1.6では、以下のような順番になっていました。ソースコードの一部を抜粋します。RC4が先頭に来ていることがわかります。
// Definition of the CipherSuites that are enabled by default.
// They are listed in preference order, most preferred first.
int p = DEFAULT_SUITES_PRIORITY * 2;
add("SSL_RSA_WITH_RC4_128_MD5", 0x0004, --p, K_RSA, B_RC4_128, N);
add("SSL_RSA_WITH_RC4_128_SHA", 0x0005, --p, K_RSA, B_RC4_128, N);
add("TLS_RSA_WITH_AES_128_CBC_SHA", 0x002f, --p, K_RSA, B_AES_128, T);
add("TLS_RSA_WITH_AES_256_CBC_SHA", 0x0035, --p, K_RSA, B_AES_256, T);
add("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", 0xC002, --p, K_ECDH_ECDSA, B_RC4_128, N);
add("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", 0xC004, --p, K_ECDH_ECDSA, B_AES_128, T);
add("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", 0xC005, --p, K_ECDH_ECDSA, B_AES_256, T);
add("TLS_ECDH_RSA_WITH_RC4_128_SHA", 0xC00C, --p, K_ECDH_RSA, B_RC4_128, N);
add("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", 0xC00E, --p, K_ECDH_RSA, B_AES_128, T);
add("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", 0xC00F, --p, K_ECDH_RSA, B_AES_256, T);
add("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", 0xC007, --p, K_ECDHE_ECDSA,B_RC4_128, N);
add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 0xC009, --p, K_ECDHE_ECDSA,B_AES_128, T);
add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 0xC00A, --p, K_ECDHE_ECDSA,B_AES_256, T);
add("TLS_ECDHE_RSA_WITH_RC4_128_SHA", 0xC011, --p, K_ECDHE_RSA, B_RC4_128, N);
add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 0xC013, --p, K_ECDHE_RSA, B_AES_128, T);
add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 0xC014, --p, K_ECDHE_RSA, B_AES_256, T);
add("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", 0x0033, --p, K_DHE_RSA, B_AES_128, T);
add("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", 0x0039, --p, K_DHE_RSA, B_AES_256, T);
add("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", 0x0032, --p, K_DHE_DSS, B_AES_128, T);
add("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", 0x0038, --p, K_DHE_DSS, B_AES_256, T);
add("SSL_RSA_WITH_3DES_EDE_CBC_SHA", 0x000a, --p, K_RSA, B_3DES, T);
add("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xC003, --p, K_ECDH_ECDSA, B_3DES, T);
add("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", 0xC00D, --p, K_ECDH_RSA, B_3DES, T);
add("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xC008, --p, K_ECDHE_ECDSA,B_3DES, T);
add("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", 0xC012, --p, K_ECDHE_RSA, B_3DES, T);
add("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", 0x0016, --p, K_DHE_RSA, B_3DES, T);
add("SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", 0x0013, --p, K_DHE_DSS, B_3DES, N);
add("SSL_RSA_WITH_DES_CBC_SHA", 0x0009, --p, K_RSA, B_DES, N);
add("SSL_DHE_RSA_WITH_DES_CBC_SHA", 0x0015, --p, K_DHE_RSA, B_DES, N);
add("SSL_DHE_DSS_WITH_DES_CBC_SHA", 0x0012, --p, K_DHE_DSS, B_DES, N);
add("SSL_RSA_EXPORT_WITH_RC4_40_MD5", 0x0003, --p, K_RSA_EXPORT, B_RC4_40, N);
add("SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0008, --p, K_RSA_EXPORT, B_DES_40, N);
add("SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0014, --p, K_DHE_RSA, B_DES_40, N);
add("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x0011, --p, K_DHE_DSS, B_DES_40, N);
Java1.7では、順番が大きく変わっており、以下のようになっています。また、この順番がどのような意図で決定されているか、コメント内で説明が加えられました。
/*
* Definition of the CipherSuites that are enabled by default.
* They are listed in preference order, most preferred first, using
* the following criteria:
* 1. Prefer the stronger buld cipher, in the order of AES_256,
* AES_128, RC-4, 3DES-EDE.
* 2. Prefer the stronger MAC algorithm, in the order of SHA384,
* SHA256, SHA, MD5.
* 3. Prefer the better performance of key exchange and digital
* signature algorithm, in the order of ECDHE-ECDSA, ECDHE-RSA,
* RSA, ECDH-ECDSA, ECDH-RSA, DHE-RSA, DHE-DSS.
*/
int p = DEFAULT_SUITES_PRIORITY * 2;
// shorten names to fit the following table cleanly.
int max = ProtocolVersion.LIMIT_MAX_VALUE;
int tls11 = ProtocolVersion.TLS11.v;
int tls12 = ProtocolVersion.TLS12.v;
// ID Key Exchange Cipher A obs suprt PRF
// ====== ============ ========= = === ===== ========
add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
0xc024, --p, K_ECDHE_ECDSA, B_AES_256, T, max, tls12, P_SHA384);
add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
0xc028, --p, K_ECDHE_RSA, B_AES_256, T, max, tls12, P_SHA384);
add("TLS_RSA_WITH_AES_256_CBC_SHA256",
0x003d, --p, K_RSA, B_AES_256, T, max, tls12, P_SHA256);
add("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
0xc026, --p, K_ECDH_ECDSA, B_AES_256, T, max, tls12, P_SHA384);
add("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
0xc02a, --p, K_ECDH_RSA, B_AES_256, T, max, tls12, P_SHA384);
add("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
0x006b, --p, K_DHE_RSA, B_AES_256, T, max, tls12, P_SHA256);
add("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
0x006a, --p, K_DHE_DSS, B_AES_256, T, max, tls12, P_SHA256);
add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
0xC00A, --p, K_ECDHE_ECDSA, B_AES_256, T);
add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
0xC014, --p, K_ECDHE_RSA, B_AES_256, T);
add("TLS_RSA_WITH_AES_256_CBC_SHA",
0x0035, --p, K_RSA, B_AES_256, T);
add("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
0xC005, --p, K_ECDH_ECDSA, B_AES_256, T);
add("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
0xC00F, --p, K_ECDH_RSA, B_AES_256, T);
add("TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
0x0039, --p, K_DHE_RSA, B_AES_256, T);
add("TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
0x0038, --p, K_DHE_DSS, B_AES_256, T);
add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
0xc023, --p, K_ECDHE_ECDSA, B_AES_128, T, max, tls12, P_SHA256);
add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
0xc027, --p, K_ECDHE_RSA, B_AES_128, T, max, tls12, P_SHA256);
add("TLS_RSA_WITH_AES_128_CBC_SHA256",
0x003c, --p, K_RSA, B_AES_128, T, max, tls12, P_SHA256);
add("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
0xc025, --p, K_ECDH_ECDSA, B_AES_128, T, max, tls12, P_SHA256);
add("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
0xc029, --p, K_ECDH_RSA, B_AES_128, T, max, tls12, P_SHA256);
add("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
0x0067, --p, K_DHE_RSA, B_AES_128, T, max, tls12, P_SHA256);
add("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
0x0040, --p, K_DHE_DSS, B_AES_128, T, max, tls12, P_SHA256);
add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
0xC009, --p, K_ECDHE_ECDSA, B_AES_128, T);
add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
0xC013, --p, K_ECDHE_RSA, B_AES_128, T);
add("TLS_RSA_WITH_AES_128_CBC_SHA",
0x002f, --p, K_RSA, B_AES_128, T);
add("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
0xC004, --p, K_ECDH_ECDSA, B_AES_128, T);
add("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
0xC00E, --p, K_ECDH_RSA, B_AES_128, T);
add("TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
0x0033, --p, K_DHE_RSA, B_AES_128, T);
add("TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
0x0032, --p, K_DHE_DSS, B_AES_128, T);
add("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
0xC007, --p, K_ECDHE_ECDSA, B_RC4_128, N);
add("TLS_ECDHE_RSA_WITH_RC4_128_SHA",
0xC011, --p, K_ECDHE_RSA, B_RC4_128, N);
add("SSL_RSA_WITH_RC4_128_SHA",
0x0005, --p, K_RSA, B_RC4_128, N);
add("TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
0xC002, --p, K_ECDH_ECDSA, B_RC4_128, N);
add("TLS_ECDH_RSA_WITH_RC4_128_SHA",
0xC00C, --p, K_ECDH_RSA, B_RC4_128, N);
add("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
0xC008, --p, K_ECDHE_ECDSA, B_3DES, T);
add("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
0xC012, --p, K_ECDHE_RSA, B_3DES, T);
add("SSL_RSA_WITH_3DES_EDE_CBC_SHA",
0x000a, --p, K_RSA, B_3DES, T);
add("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
0xC003, --p, K_ECDH_ECDSA, B_3DES, T);
add("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
0xC00D, --p, K_ECDH_RSA, B_3DES, T);
add("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
0x0016, --p, K_DHE_RSA, B_3DES, T);
add("SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
0x0013, --p, K_DHE_DSS, B_3DES, N);
add("SSL_RSA_WITH_RC4_128_MD5",
0x0004, --p, K_RSA, B_RC4_128, N);
// Renegotiation protection request Signalling Cipher Suite Value (SCSV)
add("TLS_EMPTY_RENEGOTIATION_INFO_SCSV",
0x00ff, --p, K_SCSV, B_NULL, T);
setEnabledCipherSuitesを使う
上記のようにJava1.7ではデフォルトのCipherSuiteリストの順番が変わりましたが、実行時にSSLSocketクラスのsetEnabledCipherSuites()を呼び出すことで、任意の順番にこのリストを書き換えることができます。以下のような関数を定義し、SSLSocketクラスのインスタンスを渡すことで、RC4が優先されるようになります。
public static void preferRC4( SSLServerSocket sSocket )
{
//set RC4 to front
final String RC4 = "SSL_RSA_WITH_RC4_128_SHA";
List l = new ArrayList();
l.addAll( Arrays.asList( sSocket.getEnabledCipherSuites() ) );
if( l.contains( RC4 ) )
{
String[] cipherArray = new String[ l.size() ];
cipherArray[ 0 ] = RC4;
l.remove( RC4 );
for( int i = 0; i < l.size(); ++i )
{
cipherArray[ i + 1 ] = ( String )( l.get( i ) );
}
sSocket.setEnabledCipherSuites( cipherArray );
}
}
まとめ
今回はJava1.7において、サーバ側でRC4を優先して使う方法について紹介しました。簡単にまとめると、以下のようになります。
- ServerHandshakerクラスを書き換えて、jsse.jarファイル内のクラスファイルを書き換える
- jsse.jarの位置はデフォルトの場所のままにする(JAVA_HOME/jre/lib/)
- サーバ側ではコード内でsetEnabledCipherSuitesを呼び出し、RC4を優先させる
余談:RC4の安全性について
RC4の安全性について、様々な議論が起こっているようです。しかし現時点ではまだはっきりとした決着はついておらず、「現実的にインターネット上の利用では危険はない」と考える立場の人も多いようです(私も現時点ではこの立場です)。例えばGoogleはSSLにおいて全面的にRC4を優先する設定を使用しているようです。
今回のエントリではこの問題については触れず、JavaでRC4を優先するための設定内容にフォーカスしました。フィードバックなどありましたらお気軽に@kinyukaまで。