自堕落な技術者の日記

基本は喰ってるか飲んでるかですが、よく趣味でカラオケ・PKI・署名・認証・プログラミング・情報セキュリティをやっています。旅好き。テレビ好きで芸能通

Java

JRE 1.4-1.6やAndroidのAPIを使ったHTTPS接続のCipherSuitesがRC4-MD5を優先しているかなりヤバい話

OWASP Night 8thに行ってきて、iOSやAndroidのいろんなアプリのHTTPS接続の検証がいい加減という話を聞いてきたんですが、それよりもAndroidのデフォルトブラウザChromeが失効検証をしない(=証明書の取り消し確認をしない)という事だって十分問題なんじゃないかなぁとか思ったりしてました。

そういえば以前、秋山さんにAndroidだとHTTPS通信がRC4-MD5になっちゃうという話を聞いて、実際にAndroid上のChromeやOperaやFireFoxを見て全く問題ないことを確認し「よしよし安心だ」などと思っていました。

あれれ?待てよ、秋山さんが言っていたのはブラウザではなく、APIで呼び出したときのCipherSuiteの順序関係の可能性もあるなと思ってグーグル先生に聞いて調べてみました。

(文献1)Why Android SSL was downgraded from AES256-SHA to RC4-MD5 in late 2010 (2013.10.13)
http://op-co.de/blog/posts/android_ssl_downgrade/
(文献2)The Register: Android security relies on ZOMBIE CRYPTO, argues infosec pundit (2013.10.16) http://www.theregister.co.uk/2013/10/16/zombie_cipher_list_gives_android_weak_encryption/

あらら、こりゃやべーよ。なんか検索してみても日本では誰も騒いでないみたいだよ。一ヶ月も放っぽりぱなしだったんだなぁ。自分が第一発見者なら公開せずJPCERTとかに報告するんだけどグローバルには公表から1ヶ月も経っているので、国内で騒がれてないようだから書いとくかなと。

結論から言うと、Java 1.4から1.6ぐらい、Android 2.3以降の全てのバージョンで、ウェブブラウザではなくHttpURLConnectionやApacheのHttpClientなどのクラスのAPIを使ったクライアント実装でHTTPS接続する場合には、デフォルトではRC4-MD5といったとても弱い暗号が最優先で使われるために、「必ず」明示的に暗号設定を指定する必要がある。

という事です。JavaやAndroidのHTTPSクライアントを使用する開発者の方は是非対応をお願いしたいです。

AndrodのHttpURLConnectionクラスの利用

早速、簡単なAndroidのHTTPSクライアントをHttpURLConnectionクラスを使って書いて接続し、CipherSuitesを確認してみました。Android 4.0.3で確認しましたが、やはりRC4-MD5が優先になっていました。文献1によればAndroid 2.3.4以降、Android 4.3までダメなようです。

Client Hello Version: TLS 1.0
TLS Version: TLS 1.0
Num Cipher Suites: 35
Cipher Suite: TLS_RSA_WITH_RC4_128_MD5 (0x0004)
Cipher Suite: TLS_RSA_WITH_RC4_128_SHA (0x0005)
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
Cipher Suite: TLS_ECDH_ECDSA_WITH_RC4_128_SHA (0xc002)
Cipher Suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA (0xc004)
Cipher Suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA (0xc005)
Cipher Suite: TLS_ECDH_RSA_WITH_RC4_128_SHA (0xc00c)
Cipher Suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA (0xc00e)
Cipher Suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA (0xc00f)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_RC4_128_SHA (0xc007)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
Cipher Suite: TLS_ECDHE_RSA_WITH_RC4_128_SHA (0xc011)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x0033)
Cipher Suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0x0039)
Cipher Suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA (0x0032)
Cipher Suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA (0x0038)
Cipher Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x000a)
Cipher Suite: TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA (0xc003)
Cipher Suite: TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA (0xc00d)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA (0xc008)
Cipher Suite: TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA (0xc012)
Cipher Suite: TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA (0x0016)
Cipher Suite: TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA (0x0013)
Cipher Suite: TLS_RSA_WITH_DES_CBC_SHA (0x0009)
Cipher Suite: TLS_DHE_RSA_WITH_DES_CBC_SHA (0x0015)
Cipher Suite: TLS_DHE_DSS_WITH_DES_CBC_SHA (0x0012)
Cipher Suite: TLS_RSA_EXPORT_WITH_RC4_40_MD5 (0x0003)
Cipher Suite: TLS_RSA_EXPORT_WITH_DES40_CBC_SHA (0x0008)
Cipher Suite: TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA (0x0014)
Cipher Suite: TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA (0x0011)
Cipher Suite: TLS_EMPTY_RENEGOTIATION_INFO_SCSV (0x00ff)

例えばAndroid 4.1のNativeCrypto.javaのソースコードを見ると確かにRC4-MD5が優先になっているように見えます。

Oracle JavaのHttpURLConnectionクラスの利用

文献1によれば、そのようなCipherSuiteのデフォルト設定はOracle(Sun) Java JRE/JDKの 設定から来ているもので、Java 1.4.2_19, 1.5.0 (2004)から Java 1.6 (2006)まで 弱いRC4-MD5が優先されていると書いています。

Windows版Sun JDK 1.6.0_21 b07では以下のようになっていました。確かにRC4-MD5優先です。

Client Hello Version: SSL 2.0
Client Hello Version: TLS 1.0
Num Cipher Suites: 19
Cipher Suite: TLS_RSA_WITH_RC4_128_MD5 (0x000004)
Cipher Suite: SSL2_RC4_128_WITH_MD5 (0x010080)
Cipher Suite: TLS_RSA_WITH_RC4_128_SHA (0x000005)
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x00002f)
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x000033)
Cipher Suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA (0x000032)
Cipher Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x00000a)
Cipher Suite: SSL2_DES_192_EDE3_CBC_WITH_MD5 (0x0700c0)
Cipher Suite: TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA (0x000016)
Cipher Suite: TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA (0x000013)
Cipher Suite: TLS_RSA_WITH_DES_CBC_SHA (0x000009)
Cipher Suite: SSL2_DES_64_CBC_WITH_MD5 (0x060040)
Cipher Suite: TLS_DHE_RSA_WITH_DES_CBC_SHA (0x000015)
Cipher Suite: TLS_DHE_DSS_WITH_DES_CBC_SHA (0x000012)
Cipher Suite: TLS_RSA_EXPORT_WITH_RC4_40_MD5 (0x000003)
Cipher Suite: SSL2_RC4_128_EXPORT40_WITH_MD5 (0x020080)
Cipher Suite: TLS_RSA_EXPORT_WITH_DES40_CBC_SHA (0x000008)
Cipher Suite: TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA (0x000014)
Cipher Suite: TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA (0x000011)

これが、Oracle(Sun) Java JRE/JDK 1.7.0 b147だと以下のようになり、 DHE-RSA-AES128-SHAが優先されており、1.7のとあるバージョンからは問題が直っているのだと思います。

Client Hello Version: TLS 1.0
TLS Version: TLS 1.0
Num Cipher Suites: 21
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x0033)
Cipher Suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA (0xc004)
Cipher Suite: TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA (0x0016)
Cipher Suite: TLS_RSA_WITH_RC4_128_SHA (0x0005)
Cipher Suite: TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA (0xc003)
Cipher Suite: TLS_ECDHE_RSA_WITH_RC4_128_SHA (0xc011)
Cipher Suite: TLS_ECDH_ECDSA_WITH_RC4_128_SHA (0xc002)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_RC4_128_SHA (0xc007)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA (0xc008)
Cipher Suite: TLS_ECDH_RSA_WITH_RC4_128_SHA (0xc00c)
Cipher Suite: TLS_EMPTY_RENEGOTIATION_INFO_SCSV (0x00ff)
Cipher Suite: TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA (0xc00d)
Cipher Suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA (0xc00e)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
Cipher Suite: TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA (0xc012)
Cipher Suite: TLS_RSA_WITH_RC4_128_MD5 (0x0004)
Cipher Suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA (0x0032)
Cipher Suite: TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA (0x0013)
Cipher Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x000a)

この問題を解決するには開発側でなんとかするしかない

この問題を解決するにはHttpURLConnectionで明示的にCipherSuiteやSSL/TLSのバージョンを 指定してやる必要があります。SSLSocketクラスでsetEnablesCipherSuites()メソッドを使う プリミティブな方法もありますが、プロパティで設定する方が簡単かもしれません。

System.setProperty("https.cipherSuites", 
                   "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA," +
                   "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
System.setProperty("https.protocols", "TLSv1.2,TLSv1.1");
URL url = new URL("https://www.verisign.com/");
BufferedReader in = 
  new BufferedReader(new InputStreamReader(url.openStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
  System.out.println(inputLine);
in.close();

国内でもこの問題が広く認知され、対応されるといいですね。

今日はこの辺で

(小ネタ)Android 4.0.4と4.3.1とでJCEプロバイダはどう違うのか

新しいNexus 5がリリースされ新しいOSであるAndroid 4.4 KitKatも お目見えしたわけで、Nexus 7 2013を持っている私としては 早くアップデートが出るのを心待ちにしています。

さて、アップデートが出てしまう前に、Android 4.0.4 (CUBE U20GT) と、Android 4.3.1 (Nexus 7 2013)とでJCEプロバイダがどう違うのかを みておきました。調べるのにはJCE Provider Checkerを使いました。

違いのまとめ

  • AndroidOpenSSLプロバイダ
    • Mac: HmacSHA{1,256,384,512}の追加
    • Cipher: AES,DES,3DES等追加
    • Signature: {MD5,SHA{1,256,384,512}with{RSA,ECDSA}, SHA1withDSAの追加
    • SecureRandom: SHA1RPNGの追加
    • SSLContext: TLSv1.1, TLSv1.2の追加
  • BCプロバイダ
    • バージョンが1.46から1.48へ
    • 実装クラス名やAliasの変更がほとんどか?
  • AndroidKeyStoreプロバイダが新たに追加された

AndroidOpenSSLでTLSv1.1, v1.2がサポートされるようになったのは重要ですかね。 JCE Provider Checkerはデータのコピペが面倒だとわかったのでこれは改善しないといけません。

RSAとECDSA、署名生成と署名検証どっちが速い?

2013年9月4日に開催されたOpenID Tech Night Vol.10 に参加してて、

大きなプロバイダではRSA署名の検証は結構大変です。 RSAは署名よりも検証の方が計算コストがかかるので...

みたいな話をされ、「んん?逆じゃないの。RSA署名の検証は署名生成よりも圧倒的に計算コスト低いよね。」とか 思ってたわけで「がが〜〜ん」と。 「まぁ、ちょっと調べてみるべぇ。」と手持ちのノートPCで調べてみました。

検証内容

以下のことを検証してみたいと思います。

  • 署名の生成と検証ではどちらが速いのか。RSAとECC(ECDSA)では違いがあるのか。
  • 同程度の暗号強度のRSA署名とECDSA署名ではどれくらい速度差があるのか。
  • RSA署名では鍵長が変わったとき、どれくらい速度差があるのか。
  • ECDSA署名では楕円曲線や鍵長が変わったとき、どれくらい速度差があるのか。
  • Ruby+OpenSSLとJava JCEではどれくらい速度差があるのか。
なんか、楕円「マンセー」みたいな人もおられますが、 「楕円は鍵長が短いから圧倒的に速い」みたいな事を言い出す人もいて 「ホンマかいな?」と調べてみたかったわけです。 楕円って期待するほどそんなに速くないですよね? むしろ遅いですよね。

検証方法・検証環境

言語の偏りがあるのも良くないのでOpenSSLベースのものとJava JCEベースのものと調べます。 いちいちOpenSSLをCで書くのも面倒なので。Rubyを使いました。 処理時間の測定方法については、Ruby+OpenSSL、Java JCEで次のような観点で測定しています。

共通
  • 鍵や証明書のロードの時間は処理時間に含めない。
  • 測定条件統一のためハッシュ計算の時間は処理時間に含める。
  • 同一のマシンで測定する。
  • 再利用しない同一の鍵で2000回の署名生成、署名検証の時間を計測。
  • 公開鍵暗号のパフォーマンスを知りたいだけなので署名対象は"aaa"の短い文字列。
  • SHA1withRSAもしくはSHA1withECDSAで比較する。
Ruby+OpenSSL
  • Ruby標準の'benchmark'モジュールを用い、リハーサルも行う。benchmarkのrealの時間を用いる。
Java JCE
  • イテレーションループの前後でのSystem.currentTimeMillis()の値の差を処理時間とする。
細かい検証環境情報は以下の通りとなります。
検証環境
マシンLenovo X201s
CPUIntel Core i7 L620 2.00GHz
メモリ8GB
OSMicrosoft Windows 7 Professional 32bit SP1
Java
バージョンOracle Java 1.7.0 build 1.7.0-b147
RSA署名JCEプロバイダSunRsaSign 1.7 Provider
ECDSA署名JCEプロバイダSunEC 1.7 Provider
Ruby (+ OpenSSL)
バージョンcygwin C Ruby 1.9.3p194
OpenSSLOpenSSL 1.0.1c

Ruby + OpenSSLで署名

Ruby + OpenSSLでRSAやECDSA署名するには、OpenSSLコマンドで普通に PKCS#5の秘密鍵と公開鍵を準備してこんな感じで署名生成、署名検証すればヨロシ。

# ECDSAの署名生成
prvKey = OpenSSL::PKey::EC.new(File.read(PKCS#5秘密鍵PEM))
hashed = OpenSSL::Digest::SHA1.digest(署名対象メッセージ)
sigVal = prvKey.dsa_sign_asn1(hashed)

# ECDSAの署名検証
pubKey = OpenSSL::PKey::EC.new(File.read(PKCS#5公開鍵PEM))
hashed = OpenSSL::Digest::SHA1.digest(data)
isValid = pubKey.dsa_verify_asn1(hashed, sigVal)

# RSAの署名生成
prvKey = OpenSSL::PKey::RSA.new(File.read(PKCS#5秘密鍵PEM))
sigVal = prvKey.sign("sha1", data)   

# RSAの署名検証
pubKey = OpenSSL::PKey::RSA.new(File.read(PKCS#5公開鍵PEM))
isValid = pubKey.verify("sha1", sigVal, data)   
ECDSAの時はハッシュを自分で計算するのと、 ECDSAの署名値をASN.1構造で表現することに気をつければ問題ないかと思います。

Java JCEで署名

最近、キーストア使って楽してたので普通に鍵ファイルを読みたい場合には、 PKCS#8 DERじゃないといけないんだよなとか探してみると 自分の記事 「OpenSSLで鍵生成した秘密鍵をJavaで使う」が見つかって助かりました。 秘密鍵ファイルはPKCS#8にしといて、公開鍵もPKCS#8にしようとしたら 「読めな〜〜〜い!!」鍵はパラメータの数値(BigInteger)を指定するか 証明書じゃないといけないそうだ。仕方ないから無理やり自己署名証明書を作りました。

ECDSAの署名生成はこんな感じ。

KeySpec keySpec = new PKCS8EncodedKeySpec(PKCS#8秘密鍵DERのデータbyte配列);
KeyFactory kf = KeyFactory.getInstance("EC");
PrivateKey prvKey = kf.generatePrivate(keySpec);
Signature sig = Signature.getInstance("SHA1withECDSA");
sig.initSign(prvKey);
sig.update(署名対象データaaa);
sigVal = sig.sign();

RSAの署名生成はこんな感じ。

KeySpec keySpec = new PKCS8EncodedKeySpec(PKCS#8秘密鍵DERのデータbyte配列);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey prvKey = kf.generatePrivate(keySpec);
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initSign(prvKey);
sig.update(署名対象データaaa);
sigVal = sig.sign();

RSAやECDSAの署名検証はこんな感じ。

CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cer = (X509Certificate)cf.generateCertificate(new FileInputStream(公開鍵証明書));
pubKey = cer.getPublicKey();
Signature sig = Signature.getInstance("SHA1withECDSA"); // RSAならSHA1withRSA
sig.initVerify(pubKey);
sig.update(署名対象データ);
isValid = sig.verify(sigVal);

最初、BouncyCastle使えばいいかとも思ったんですが、 Java SE 7から楕円用のプロバイダSunECが標準提供されるようになったので、 Java SE 7の標準バンドルされたプロバイダを使うことにしました。 サポートしている楕円曲線はどうだっけと思ったら 前に自分で調べてあったのでそれを参考にしました。 ( 祝 Java SE 7 リリース記念「JCEはどうなってんの?」)

RSAとECCの暗号強度対応

一般に、

  • ECC 160bit は RSA 1024bitに相当する、とか
  • ECC 256bit は RSA 3072bitに相当する、とか
言われていますが、調べて見ると、 NIST SP800-57 Recommendation for Key Management - Part1: Generalの 5.6.1節 Comparable Algorithm Strengthで対象暗号、RSA、DSA、ECC(ECDSA)の鍵長と 暗号強度の対応の表があり、 RFC 5656 Elliptic Curve Algorithm Integration in the Secure Shell Transport Layerでも引用してます。(こっちの方が見やすい)

表を引用しておきましよう。

共通鍵暗号DSARSAECDSA
80 L=1024,N=160 1024 160-223
112L=2048,N=256 2048 224-255
128L=3072,N=256 3072 256-383
192L=7680,N=384 7680 384-511
256L=15360,N=51215360512-

(結果1)RSAの署名生成と署名検証はどっちが速いか

まずはRSA鍵での署名と検証がどれくらい違うのか見てみましょう。


cmp1-rsa-sign-verify

署名と生成では、署名の方が圧倒的に時間がかかることがわかります。 また、鍵長が長くなるほど指数関数的に時間がかかることがわかります。 署名生成に関しては特にJava JCEの遅さが顕著です。

(結果2)ECDSAの署名生成と署名検証はどっちが速いか

グラフからECDSAではRSAとは逆に署名検証より署名生成の方が わずかながら速いですが、あまり変わらないということがわかります。 また、同じ鍵長のsecp160r1, secp160r2, secp160k1とでは ほとんど処理速度には違いはなく、鍵長が長くなると処理時間は増えますが、 RSAが非常に鍵長の影響を受けるのに対し、ECDSAでは あまり鍵長が長くなっても処理時間が長くはならない事がわかります。


cmp2-ecdsa-sign-verify

(結果3)同じ暗号強度ではRSAとECDSAとどちらが速いか

ECC 160bit とRSA 1024bitはほぼ同等の暗号強度です。 ECDSAと比較して、RSAは署名生成はとても遅いが、署名検証は とても速いことがわかります。


cmp3-ecc160rsa

鍵長が長いケース、ECC 256bit と同等なRSA 3072bit を比較してみると、 順序関係は変わりませんが、鍵長が長くなった分、 非常にRSA署名の生成時間が長くなっています。 これに対し、ECDSAではRSAほどは鍵長が長くなった影響を受けていません。


cmp4-ecc256rsa

まとめ

簡単にまとめると時間がかかる順に

  • RSA署名の生成には非常に時間がかかる
  • ECDSAの署名検証は署名生成よりほんの少し長く時間がかかる
  • ECDSAの署名生成は普通に時間がかかる
  • RSA署名の検証は非常に短時間であるECDSAの署名生成は普通に時間がかかる
  • RSAでは鍵長が長くなるほどその傾向が顕著になる。
  • ECDSAはRSAほどは鍵長が長くなる影響を受けない。
といった感じでしょうか。認識してたとおりで良かったなぁと思いました。今日はこの辺で。

Apache HarmonyのJSSE(Java Secure Socket Extension)

JavaでSSL/TLS通信をしたい時に通常はJSSE(Java Secure Socket Extension)という仕組み使うんですが、これ使えば、実装色々切り替えたり、追加したりなんてことができます。Apache Tomcat(+Sun Java JSSE)だとCamellia系のCipherSuitesに対応してないそうで、何か対応してるもの、追加で対応できそうなものを調べてみました。

Sun Javaで使えるJSSEってあんまり選択肢がないのね

下の例のようにJSSEプロバイダ名を指定することによりJSSEを切り替えればいろんなSSLエンジンを試せるわけですが、

SSLContext ctx = SSLContext.getInstance(アルゴリズム名, プロバイダ名);
SSLContext ctx = SSLContext.getInstance("SSL", "SunJSSE");
SSLContext ctx = SSLContext.getInstance("TLS", "HarmonyJSSE");
Sun Javaで使えそうなJSSEの選択肢って少ないんだなぁってことが調査してみるとわかりました。Sun Javaで使えそうなのは標準品を含めて以下の3つぐらいしかなさそう。 暗号アルゴリズム系のためのJCE対応のライブラリは潤沢にある中、JSSEの方は選択肢がすくないですね。

なんか調べてみるとJSSEはJava VMに依存している方が多いんじゃないかって 感じがします。

新しいCipher Suitesを追加したいと思ったら、Sun Java用にJSSEを書き起こすっていう 手もありですが、コード署名しないと何かと面倒なのでおススメではありません。 JESSIEでやろうとするとGNU classpathなんでちょっとなぁ、、、と。JESSIE自体、 あんまりメンテナンスしてないみたいだし。 で、最終的にApache Harmonyを調べてみるのがいいのかな、、と思ったわけです。 Apache HarmonyはオープンソースでフリーなJavaの実装だそうです。

Apache HarmonyとTomcatを試してみる

Apahce HarmonyのSSL/TLSの機能というか、JSSEの機能は暗号ライブラリとしてBouncyCastle JCE Libraryを使用しており、SunJCEは使えません。 従ってHTTPSのためのSSLサーバー証明書と鍵はJKS形式はSun独自の形式なので使えなくてBouncyCastle JCEがサポートしている PKCS#12かBKS(BouncyCastle Key Store)形式を使う必要があります。 ただ、OpenSSLで生成したPKCS#12でやってみたところMacエラーだったりで読み込めず BKSを使うのがよいのかもしれません。 JKSからBKSへの変換ツールは「ここ」に置いてみたので使ってみてください。

conf/server.xmlファイルにはこんな感じでkeystoreFile、keystorePass 、keystoreTypeを設定します。

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
 maxThreads="150" scheme="https" secure="true"
 clientAuth="false" sslProtocol="TLS"
 keystoreFile="conf/zzz.bks"
 keystorePass="changeit"
 keystoreType="BKS" />

あとはbin/catalina.batに以下のようなパス設定をしとけばApache Tomcat + Apache Harmonyは動くと思います。
set CATALINA_HOME=C:\Java\apache-tomcat-6.0.30-wharmony
set JAVA_HOME=C:\harmony-6.0-jdk-991881
set JRE_HOME=C:\harmony-6.0-jdk-991881\jre
ウェブブラウザ等でHTTPSでつなぐことはできたでしょうか?

で、OpenSSLで繋いでみる

openssl s_client機能を使ってApache Tomcat + Harmonyのサーバーに接続してみましょう。

% openssl s_client -host zzz.com -port 8443
CONNECTED(00000003)
depth=1 /C=JP/O=HOGE
verify error:num=19:self signed certificate in certificate chain
verify return:0
14052:error:04077068:rsa routines:RSA_verify:bad signature:rsa_sign.c:209:
14052:error:1408D07B:SSL routines:SSL3_GET_KEY_EXCHANGE:bad signature:s3_clnt.c:1447:
とJava実装やブラウザだと出てこないエラーが出てきてしまいました。ググってみるに-cipherを設定しないことが 原因のようで-cipher 'ALL:!kEDH'を指定してちょっと絞り込みをしないといけないそうです。
% openssl s_client -host zzz.com -port 8443 -cipher 'ALL:!kEDH'
CONNECTED(00000003)
中略
depth=1 /C=JP/O=HOGE
verify error:num=19:self signed certificate in certificate chain
verify return:0
read R BLOCK
DONE
これでApache Tomcat + HarmonyにOpenSSL s_clientで繋ぐことができるようになりました。

Apache HarmonyのJSSEのコード

Apache HarmonyのJSSEはjre/lib/boot/x-net{.jar,-src.jar}にある org.apache.harmony.xnet.provider.jsse パッケージがそれに当たります。 x-net.jar自体はコード署名されてないようなので、 CipherSuiteを追加し放題で x-net.jarに入れ替えても動きそうな雰囲気です。

JSSEプロバイダの定義はJSSEProvider.javaにあります。 コメントのところに割と詳しい説明があって以下のようなものが書かれています。

  • サポートしているCipherSuites:別表
  • 使用する共通鍵暗号アルゴリズム:IDEA,RC2,RC4,DES,3DES
  • 使用するMacアルゴリズム:MD5, SHA-1
  • 使用する鍵交換アルゴリズム: RSA, DH, DHE
Apache Harmony 6.0 build 991881のHarmonyJSSEプロバイダーで提供されているアルゴリズムとその値の一覧は以下の通りのようです。
SSLContext = TLS(=TLSv1)
KeyManagementFactory = X509
TrustManagementFactory = X509
HarmonyJSSEがサポートしているCipherSuiteの一覧は以下の通りです。
TLS_NULL_WITH_NULL_NULL
TLS_RSA_WITH_NULL_MD5
TLS_RSA_WITH_NULL_SHA
TLS_RSA_EXPORT_WITH_RC4_40_MD5
TLS_RSA_WITH_RC4_128_MD5
TLS_RSA_WITH_RC4_128_SHA
TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5
TLS_RSA_WITH_IDEA_CBC_SHA
TLS_RSA_EXPORT_WITH_DES40_CBC_SHA
TLS_RSA_WITH_DES_CBC_SHA
TLS_RSA_WITH_3DES_EDE_CBC_SHA
TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA
TLS_DH_DSS_WITH_DES_CBC_SHA
TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA
TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA
TLS_DH_RSA_WITH_DES_CBC_SHA
TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA
TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
TLS_DHE_DSS_WITH_DES_CBC_SHA
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
TLS_DHE_RSA_WITH_DES_CBC_SHA
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
TLS_DH_anon_EXPORT_WITH_RC4_40_MD5
TLS_DH_anon_WITH_RC4_128_MD5
TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA
TLS_DH_anon_WITH_DES_CBC_SHA
TLS_DH_anon_WITH_3DES_EDE_CBC_SHA
そして、そのうちデフォルトの設定で提供するCipherSuiteは以下の通りです。
TLS_RSA_WITH_RC4_128_MD5
TLS_RSA_WITH_RC4_128_SHA
TLS_RSA_WITH_3DES_EDE_CBC_SHA
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
TLS_RSA_WITH_DES_CBC_SHA
TLS_DHE_RSA_WITH_DES_CBC_SHA
TLS_DHE_DSS_WITH_DES_CBC_SHA
TLS_RSA_EXPORT_WITH_RC4_40_MD5
TLS_RSA_EXPORT_WITH_DES40_CBC_SHA
TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
今晩はこんなところで、、、
ではでは

RSA BSAFE Share for Java 1.1のリリース

フリーのJavaの暗号ライブラリRSA BSAFE Shareがバージョンアップされたそうです。

変更点は以下のようです。

- EC DRBGアルゴリズムの乱数生成器
- 乱数生成器の場合にHSMが使える
- JCE APIを使ったCSRの生成 (PKCS#10, Certificate Request Message Format)
- JCE APIを使ったCertificate Management Protocolの利用
- 外部セッションキャッシュにおけるセッション情報保持のサポート
- RFC 5430に準拠したSuite-Bのサポート

うぅ〜〜む、PKCS#10ぐらいですかね?

あまり、注目するところは他には無いかと、、、、

RSA BSAFE Shareを試してみました(その6)

忘れた頃にやってくるBSAFE Shareの連載ですが、前回に引き続き証明書のパス検証関連で、今回は「BSAFE Shareの事前生成されたOCSPを使ったパス検証機能」を試してみたいと思います。

Java JCEのパス検証に対応したプロバイダ



Java JCE の証明書のパス構築(CertPathBuilder)、パス検証(CertPathValidator)をサポートしている一般に入手やすそうなJCEプロバイダとして

・SUN (Sun Java J2SE 1.4以降に含まれる)
・RSA BSAFE Share
・BouncyCastle
・IBM Java

の3つがあります。(IAIKやEntrust Toolkitはパス構築・検証についてJCEとは別のAPIを提供しています。)

RSA BSAFE Shareには、フツーのPKIXパス検証のオプションとして、OCSPを使った検証にも対応しているんですが、他のプロバイダには無い機能として

事前に取得していたOCSPレスポンスのデータを使ってパス検証ができる


っていうのがあります。Windows Vista以降はやっていますがOCSPレスポンスをキャッシュしておき再利用して検証するなんていう芸当がJavaでもできるっていうわけです。

これは、特にCAdESやXAdES長期署名をやっている人は食付いてきますよね(−−;CAdESやXAdESでは署名者証明書の検証情報(ルートまでの証明書のチェーン、CRL、OCSPレスポンス)を一緒にいれておいて将来検証に使ったりするのでデータとして渡されたOCSPレスポンスを使ってパス検証する必要があるわけです。ところが、SUNのプロバイダはオンライン取得しかサポートしていないのでこれができなかったわけです。

OCSPに対応したCAdESやXAdESの実装をやってらっしゃる方は、OCSPレスポンスのデータを使ってパス検証する仕組みを(どれくらい真面目にやっているかは別にして)実装されているんじゃないかと思います。(私もそうです)

OCSP応答ファイルを使った検証サンプル



BSAFE Shareのサンプル(sample/src/jce/certpath/ValidatePathUsingOCSPResponse.java)というのがあります。サンプルでは

・サブCA証明書(CAphoenix.crt)
・EE証明書(janet.crt)
・EE証明書検証用のOCSPレスポンス(ocspResponse.rsp)

が使われています。例によって(Javaで書くよりも余計な飾りが無い分全体の動きが見やすいと思うので)Jythonでサックリ書いてみると、、、、

# (0) BSAFE Share JCEプロバイダ設定して...
Security.insertProviderAt(JsafeJCE(), 1)
# (1) ファイル読んで...
cf = CertificateFactory.getInstance("X.509", "JsafeJCE")
caCert = cf.generateCertificate(FileInputStream("CAphoenix.crt"))
eeCert = cf.generateCertificate(FileInputStream("janet.crt"))
ocspBytes = 何らかの方法でOCSP応答"ocspResponse.rsp"をbyte配列で読んで
# (2) CertPath作って...
chain = ArrayList()
chain.add(eeCert)
certPath = cf.generateCertPath(chain)
# (3) CertPathParameter作って...
# (3.1) トラストアンカ作って...
ta = TrustAnchor(caCert, None)
trustAnchors = HashSet()
trustAnchors.add(ta)
pkixParams = PKIXParameters(trustAnchors)
# (3.2) OCSPパラメータ作って..
ocspParams = OCSPWithResponseParameters(ocspBytes, caCert)
# (3.3) CertPathパラメータ作って...
certPathParams = CertPathWithOCSPParameters(pkixParams, ocspParams)
# (4) 検証っと、、、、
certPathValidator = CertPathValidator.getInstance("PKIX", "JsafeJCE")
print certPathValidator.validate(certPath, certPathParams)


こんな流れになります。

(1) 証明書やOCSPを読み込んで
(2) CertPathオブジェクトを生成
(3) 検証用のPKIXParameterとOCSPパラメータを生成
(4) 検証

検証に使うOCSPレスポンス



OCSPのデータを使って検証する場合に、OCSPResponseを使うのか、BasicOCSPResponseなのかというのは気になるところです。詳しくはRFC 2560を見てもらうとしてざっくりこんな違いがあります。

・OCSPResponse
 ・BSAFE OCSPWithResponseParameters ではこれを用いる
 ・OCSPで返されるメッセージの全体
 ・中に成功したかどうかという状態情報(responseStatus)を含む
 ・通常は中にBasicOCSPResponseを含む
 ・XAdESではOCSP検証情報としてこれを用いる
 ・OpenSSLでもファイルとして検証できる
・BasicOCSPResponse
 ・単数または複数の証明書の有効状態
 ・OCSPResponseに含まれる
 ・CAdESではOCSP検証情報としてこれを用いる
 ・OpenSSLではファイルとして検証できない

このように、書いてしまった通り、BSAFE Share で検証させる場合にはOCSPResponseを使わなければなりません。CAdESの検証に使う場合にはBasicOCSPResponseからresponseStatus: successful をくっつけてラップしてOCSPResponseを作ってやらないといけません。

OCSPResponseかBasicOCSPResponseかを見分けるには dumpasn1 で表示させたときに先頭の辺にENUMERATED 0とあればOCSPResponse

SEQUENCE { ← OCSPResponseの始まり
ENUMERATED 0 ← responseStatus: successful
[0] {
SEQUENCE {
OBJECT IDENTIFIER ocspBasic (1 3 6 1 5 5 7 48 1 1)
OCTET STRING, encapsulates {
SEQUENCE { ← BasicOCSPResponseの始まり
SEQUENCE {
[1] { ← ResponderID.ByName
SEQUENCE {
SET {
SEQUENCE {
OBJECT IDENTIFIER countryName (2 5 4 6)
PrintableString 'FR'
  :以下略


無ければ BasicOCSPResponse

SEQUENCE { ← BasicOCSPResponseの始まり
SEQUENCE {
[1] { ← ResponderID.ByName
SEQUENCE {
SET {
SEQUENCE {
OBJECT IDENTIFIER countryName (2 5 4 6)
PrintableString 'FR'
}
  :以下略


ということになります。

ちょっと残念なところ



最初サンプルコードを見たときちょっと怪しいなぁ、、と思ったんですが、サンプルでは検証対象が

・SubCAがトラストアンカ
・SubCA→EEの一段のみ
・OCSPレスポンダ証明書はSubCA証明書と同じ

となっているんです。サンプルを見てみると

ocspParams = OCSPWithResponseParameters(ocspBytes, caCert)


つまり、パス中に入れられるOCSPレスポンスは高々一つだけって事です。Root→SubCA1→SubCA2→EEみたいな多段の証明書チェーンの場合にSubCA1、SubCA2の検証にはOCSPは使えないってことを意味しますし、OCSPレスポンダ証明書が事前にわかっているケースでしか使えないってことがわかります。

どうやら、証明書とOCSPレスポンスをごそっと投げて「検証お願いっっ!(はぁと)」というわけにはいかないようです。

途中までCRLでいいや、、、って場合にはPKIXParametersにCollectionStoreを追加してCRLをぶちこんでやれば検証できると思います。

まとめると

・OCSPレスポンダ証明書のパス検証はしない
・事前にOCSPレスポンダ証明書がわかっていなければいけない
・多段のOCSPは使えない

っていうことなんで前処理をしっかりやらないと汎用的なパス検証に使うのは面倒くさい感じですよね。

今、自前でもっているパス検証のやつは

・OCSPレスポンダ証明書を事前に知っておく必要はない
・OCSPレスポンダ証明書のトラストアンカからのパス検証を行う
・コレクションストアに入れていれば適切なOCSPレスポンスを選ぶ
・中間CAのOCSPレスポンスデータによる検証にも対応する
・OCSPのモデル(直接、委譲等)は問わない

というようになっているので、う〜〜〜む、最初はよさげと思ったんですが、結局当面は自前のを使うことになりそう、、、

NIST PKITSと似たやつでOCSPを含むパス検証でまともなテストケースが欲しいなぁ、、、、、時間を見つけて作るかなぁ、、、、

OATH関連でごにょごにょと、、、(その4,多分最終回)

前回、Javaでちゃんと動くことが確認されたので、週末にC#でワンタイムパスワードのソフトウェアトークンを書いてみました。見てくれは時間があるときもう少しオシャレにするとして、とりあえずこんな感じ、、、

softtoken02


nec_0003



何回ボタン押してもハードウェアトークンのと同じ値がちゃんと出てきます。にょほほ、、、、

RFC 4226 HOTPアルゴリズムのJavaからC#の移植は簡単だった、、、、というより、Javaのコードなんか全く参考にしないで書いてしまいました。

符合なし(unsigned)は楽チン


今回のやつをやってみて、Javaが使いにくいと思うのは符号なし整数(unsigned int, unsigned long)がプリミティブとして扱えなくて、byte型が符合(+、−)を持っちゃっている所です。

C#は符合無しのulongのuintもあり、byte型はデフォルトで符合無しの数になっています。もちろん符合付きのlong, int, sbyteもあります。符合無しを選べる分、符合を気にした余計な処理も必要なくてスッキリとしたプログラムになりました。行数も2/3ぐらいにはなっているでしょうか。

暗号アルゴリズム


今回のは共通鍵コンテナを読むのにAESとHmacSHA1を、HOTPにはHmacSHA1を使うんですが .NET ではあっさり問題なくでいました。AESについてはブロックモードとパディングがどうなんかなぁ、、、と思いましたが、何も設定しないで.NETのRijndaelManagedクラスを使えばオーケーでした。これ使えば"AES/CBC/PKCS5Padding"になるということなんだと思います。

逆に、ブロックモードとパディングを設定したい場合にはどうなっちゃうんでしょうね???

OATHについては、やりたかった事は大体できたので連載的なものは今回でおしまいってことで、、、m(_ _)mとりあえず、.NETのXPathも使いこなせるようになったんで良かったです(−−;

OATH関連でごにょごにょと、、、(その3)

自堕落な技術者の日記 : OATH関連でごにょごにょと、、、(その2) - livedoor Blog(ブログ)


OATH関連でちょっとPOCをやっていたんですが、昨晩、やはりというか、案の定というかまったく問題なくワンタイムパスワードが作れてしまった。カウンタの値もわかってしまう(−−;改めて共通鍵の配布の仕方ってホント需要なんだなぁと再認識した次第、、、、

ソフトウェアトークンみたいなもんですね、、、、、う〜〜〜む、どうしたものか、、、、(謎)

POCはJavaだったけど、C#でもAESやHMACやXML扱えるので書き直してツール仕立てにするかな、、、、

RSA BSAFE Shareを試してみました(その5)

前回からいい感じに回数が開きましたが、またRSA BSAFE Shareの話を、、、、

PKIX CertPathBuilderはかなり良い感じ



前回はNIST PKITSを使ってRSA BSAFE ShareのPKIX CertPathValidator(パス検証)を試してみましたが、今回はPKIX CertPathBuilder(パス構築)の方をためしてみました。

RSA BSAFE ShareのPKIX CertPathValidatorはSUNと比べてエラーの理由がわからんとダメ出しをしてしまい、同様にSUNのPKIX CertPathBuilderにも失敗の原因がさっぱりわからん!!!とダメ出しをしているですが今度はどうかな、、、、、

結果的に言うとBSAFEのPKIX CertPathBuilderは失敗の理由がバッチリわかるのでSUNなんかより良いです。

テストに使うパスは昔つかったスイカ型メッシュモデル風のパスです。



RSA BSAFEのCertPathBuilder、CertPathValidatorの困ったところは"-Djava.security.debug=cerpath"フラグを使ってもデバッグメッセージを全く吐かないところです。中の動きが見えにくいし、、、、、

一応デバッグフラグをつけておくと、SunのX509CertSelectorのログだけは出力されます。でデバッグ出力からパスの繋ぎ方を見る方法ですが、

certpath: X509CertSelector.match(SN: 証明書シリアル
Issuer: 発行者DN
Subject: 主体者DN)
certpath: X509CertSelector.match returning: true


の行があれば、この証明書がパス構築に使われたことを示します。それをログの上から順に見ていきます。

で、そのスイカ型モデルでパス検証をしてログを見てると、こんな感じの順序で証明書を使っているのがわかります。

cpb_rsa01



つまり、RSA BSAFE Share for JavaのPKIX CertPathBuilder実装は:
・SUNと同じエンドエンティティからルートに向けた構築
・SUNと同じ深さ優先探索
であることがわかります。

図のGGGから辿るパスは3通りあるわけですが、SUNの場合には2本辿ってダメで次に成功だったわけですが、RSAの場合には1回失敗した後で成功のパスを辿っています。

CollectionStoreの格納元にはArrayListを使っていたんですが、その格納順序で3本中のどれを選ぶかが変わるのかなと思って試してみましたが、どうもそうではないようです。

で、私の気になっているパス構築失敗時のエラーメッセージですが、ちゃんとエラーメッセージ出力するんですよ。当たり前だけど、それがSUNではできてないので、エライじゃないですか(^^v先ほどのスイカモデルで途中の証明書をなくしておいて失敗するように設定してみると、、、、

■有効なパス中のROOT→CCCの証明書を削った場合:
 java.security.cert.CertPathBuilderException:
  Sat May 02 10:26:08 JST 2009 is before Sun Jan 02 09:00:00 JST 2000
  ちゃんと有効期限のエラーが表示されます。
■上からさらにROOT→AAAの証明書を削った場合:
 java.security.cert.CertPathBuilderException:
  Expected a CA certificate.
  基本制約cAフラグのエラーっぽい感じがしますよね。

■SUNの場合ならいずれも全く無意味な以下の例外(−−;
 sun.security.provider.certpath.SunCertPathBuilderException:
  unable to find valid certification path to requested target
  こりゃなんの事かさっぱりわからん



Java実装のSSLのクライアントで「繋がんね〜〜〜」とか悩んでいる場合には、SUNのはやめてRSA BSAFEを使うよう優先して調べるといいんじゃないっすかね。

RSA BSAFE Shareを試してみました(その4)

前回に引き続きRSA BSAFE Share for Javaのお話を、、、、

NIST PKITSをやってみると



BSAFE Shareには"JsafeJCE"という名前のJCE(Java Cryptography Extension)プロバイダがあって、その中で証明書が信頼するルート証明書から辿って有効であることを検証する認証パスの構築や検証の実装が含まれています。

証明書のパス検証については米国標準技術局(NIST)がPKITSというテストケースとデータのセットを公開していてSUNのJCEプロバイダだとどうなんだ?みたいな話は4月13日に書きました

じゃぁ、JsafeJCEプロバイダのCertPathValidatorのPKIXアルゴリズムの実装はどうなんだろうって、、、、気になりますよねぇ、、、ならないか、、、、

個人的に気になったのでやってみました。で、結果は、、、、、、




(−−;

拍子抜けというか、期待が大きかっただけにがっかりというか、実装に問題があったとかそういうのではなく結果としてSUN JCEの実装と殆どサポート範囲も含めてうり二つなんです。(だから、ブログに書きにくい、、、、結果が違ってたのはDeltaCRLのテスト1つだけ)

NIST PKITSのテスト結果からSUNとJsafeJCEのJCEプロバイダには以下のような共通の特徴があります。
・あるCAが証明書とCRLの発行を別の鍵(と証明書)で行う事の非サポート
・NewWithOldのテストの不具合(詳細は見てない)
・IndirectCRLの非サポート
・DeltaCRLの非サポート
間違ったテストも全く同じなので同じ開発元が作ってるのかな?とさえ思えてしまいます。

SUNよりもJsafeJCEの方が例外のメッセージも不親切気味なので、エラーが起きたときの原因の究明には手間取るかもしれません。以下が同じテストケースにおける例外メッセージの例です。

SUN: signature check failed: Signature does not match.
JsafeJCE: Certificate verify failed!
SUN: timestamp check failed: NotBefore: Jan 01 2047
JsafeJCE: Apr 29 2009 is after Jan 01 2047 (これは互角)
SUN: CA key usage check failed: keyCertSign bit is not set
JsafeJCE: Certificate verify failed!


どうです?「Certificate verify failed!」の一言で片付けられちゃうケースが多くて微妙な感じですよね(^^;CertPathValidatorに関しては、積極的にこれを利用する理由がいまひとつ見えてきませんでした。

楕円暗号の鍵生成ができない?!


RSA BSAFE Share for Javaでは楕円暗号の署名や証明書の検証、鍵交換(ECDSAやECDH)なんかができるんですが、JCEのサポートアルゴリズムのリストを見た限りでは楕円の鍵生成機能を提供していないようです。

楕円の証明書発行要求もできないのでは?!と思うんですが、どうでしょう、、、、


今日は、休日なんでこの辺で、、、、(−−;

<お詫びと訂正 2009.04.29 17:48>
私がアルゴリズム名を集計した表に誤りがありJsafeJCEでもちゃんと楕円の鍵生成はできます。混乱させてしまい申し訳ありませんでした。




最新記事
Categories
Archives
Twitter
記事Google検索

本ブログ内をGoogle検索
Yahoo!アクセス解析
Travel Advisor
記事検索
QRコード
QRコード
  • ライブドアブログ