自堕落な技術者の日記

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

Java

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でもちゃんと楕円の鍵生成はできます。混乱させてしまい申し訳ありませんでした。




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

今日は、その他雑多なわかった事、前に書き忘れていた事をつらつらと書いてみたいと思います。

サンプルのビルド



そういえば、サンプルプログラムのビルドについて説明してませんでしたね。

結構豊富なサンプルが用意されています。

サンプルのビルドはEclipseで行います。サンプルが使用するデータファイルのパスが絶対パスで"/data/..." だったりするようなので、Unix系でないとサンプルが動かないかもしれないです。(面倒なので試してない、、、)

Eclipseのメニューから「ファイル>インポート」を選びウィザードで「一般>既存プロジェクトをワークスペースへ」でできたと思います。

サンプルのビルドは同じくメニューから「プロジェクト>プロジェクトのビルド」でビルドできたと思います。

なんでAntじゃないんだろう、、、プンプン、、、、

FIPS 140-2 Level1認定は無い



RSA BSAFE ShareはFIPS 140-2 Level1認定は無いそうで、これが必要ならば商用ライセンスが必要なんだそうです。RSA BSAFE ShareでもFIPSモードみたいなのはあるみたいなんですが、これを設定するとエラーになったりするんでしょうか。

時間がある時、調べてみたいと思います。

そもそもDevelopper's Guideは読め



さすが基本的にちゃんとした商用ライブラリなんでIAIKやBouncyCastleなんかと違ってドキュメントは充実してました。まずはDevelopper's Guide("doc/dev_guide/index.html")を読むべきでしたね。

SSLのCipherSuiteやサポートする暗号アルゴリズム、鍵長なんかもちゃんと記載されています。(JCEのアルゴリズム名はありませんが、、、)

TLSv1.2用のSuite-B AES-GCMを使ったCipherSuiteもちゃんとサポートされていました。(疑ってゴメンよ)

ちなみに、このガイドはdoxygenを使って書かれてるみたいです。

パス検証でEV証明書のチェッカーも付属



HTTPSで接続したときブラウザのアドレスバーが緑色になるEV(Extended Validation)証明書であるかどうかチェックするためのAPIもついていました。EV Guidelineで規定された証明書プロファイルに準拠しているか(アルゴリズム、鍵長も含めて)チェックしているみたいです。

ASN.1、CMS、PKCS#7なんかは無い



RSA BSAFE Share for Javaは

・暗号プリミティブ(暗号,署名,ハッシュ,メッセージ認証,乱数)
・SSL/TLS通信
・証明書検証

のためのライブラリであって

・CMS署名/暗号フォーマット
・PKCS#7署名/暗号フォーマット
・XML署名/暗号
・ASN.1
・証明書拡張の情報の取得

は守備範囲外みたいです。

CMS、PKCS#7はわかりますが、証明書ポリシのpolicyQualifierとか証明書の拡張を取り出して中身を見るには別のライブラリを使う必要があります。

JavaのJurisdiction Policyのチェック



ちょっとオモシロイと思ったので、これも紹介、、、、

Sun Javaは暗号ライブラリを含みワッセナー条約の輸出規制にかかるため、配布時点では管轄ポリシー(jurisdiction policy)ファイルで制限されています。

規制の対象外の殆どの国では、このポリシーファイル(jre/lib/security/{local,US_export}_policy.jar)を無制限強度(unlimited strength)のものに置き換えることで、鍵の長い強固な暗号を使えたりします。

BSAFEではこのポリシーのチェックのためのクラスがあって

# Jythonでスンマソン
from com.rsa.jsafe.crypto import *
print PolicyChecker.JurisdictionPolicyLevel.getJurisdictionPolicyLevel()


を実行してみると "UNLIMITED" と表示されたりします。

ちゃんとこれをプログラムでチェックできるのは本当にいいですよね。Java関係のいろんな製品のサポートとかでもJurisdiction Policyファイルを入れ替えてなかったせいでエラーや動かなかった報告はよく聞きますから、、、、

なんか、脈絡なくてごめんなさいね。今日は、この辺で、、、、、、

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

前回はRSA BSAFE Share for Javaを使えるように、プロバイダの設定や簡単なサンプルなんかを紹介しました。

前回、手持ちのJCEプロバイダで実装しているアルゴリズムの一覧表を作っていて、RSA BSAFE Shareについても加えてみたっていう話をしましたが、他のJCEプロバイダと比較してみて、各インタフェースクラスのRSA BSAFE Share for Java JCEプロバイダ実装の特徴をまとめるとこんな感じ、、、、

・署名/Signature: 代表的なものはカバー、SHA2、楕円もサポート
・暗号/Cipher: 代表的なものはカバー、珍しいとこではECIESwith*
・ハッシュ/MessageDigest: SHA2を含め代表的なものはカバー。RIPEMDは160のみ。
・安全な乱数生成器/SecureRandom: 他プロバイダと比較できない程種類が多い
・パス構築/CertPathBuilder: 他プロバイダには無いPKIX-SuiteB*, X.509V1をサポート
・パス検証/CertPathValidator: 他プロバイダには無いPKIX-SuiteB*, X.509V1をサポート
・メッセージ認証/Mac: HmacMD*, HmacSHA* などメジャーどこは全てカバー
・SSL通信内容/SSLContent: SSL*, TLSv1 など主要はサポート。TLSv1.2がある。
・SSL用トラストアンカ管理/TrustManagerFactory: PKIX-SuiteB*ある。

というわけで、暗号アルゴリズムはメジャーどこは全て押さえてあり、そつがない印象で幅広いケースで安心して使えるJCEライブラリになっています。

SecureRandomは種類が多いのはイイですが、優れたものが一つあれば十分で、交換したり、生成、検証したりしないので相互運用性が問題になることも無く、多かろうが少なかろうが、まぁ、あまり問題ではないかなと、、、、

やはり注目すべきなのは、他のJCEプロバイダにはない、パス構築・検証でのSuite-Bのサポートだと思います。

Suite-Bとは?



最近ちょっと話題になりつつある「Suite-B」とは、米国国家安全保障局(NSA)で定められた官民双方で利用できる一般的な機密保護のための仕様の公開された暗号アルゴリズムのセットです。さらに重要性の高い用途のためにNSAで開発された仕様非公開のアルゴリズムによる「Suite-A」というセットもあります。

・Suite-A
 ・よりセンシティブな機密情報を扱うための暗号アルゴリズムのセット
 ・NSAにより開発され仕様の公開されていない暗号アルゴリズムで構成される
 ・ブロック暗号(Juniper)、非対称鍵暗号(MayFly)など
・Suite-B
 ・一般的な機密情報を扱うための暗号アルゴリズムのセット
 ・仕様の公開されている現在強度の高いとされる暗号アルゴリズムにより構成される

Suite-B は以下のパラメータの暗号アルゴリズムにより構成されています。

・共通鍵暗号: 鍵長128bitか256bitのAES-GCM暗号
・電子署名: ECDSA (楕円)
・鍵共有: ECDH (楕円Diffie-Hellman)
・ハッシュ関数: SHA-256とSHA-384

インターネットの標準であるRFCでは最近、IPsecでSuiteBを使うためにRFC 4969が出ていたり、S/MIME暗号メールでSuiteBを使うためのRFCが出ていたりします。

Windowsでは、Vista以降のCryptoAPIの後継であるCNGでSuiteBをサポートしていたり、Outlook2007でSuiteBしか使わないグループポリシにしたり、IPsecでSuiteBを使うようにしたり、認証局の証明書テンプレートでSuiteBを使うようにしたりできるようです。

BSAFE Shareのパス検証のSuiteB対応



指定された時刻に証明書のチェーンが正しく繋がっていて、失効しておらず、有効期限内であるかを確認するためにパス構築・検証を行います。

パス構築・検証でSuiteBの暗号しか使わないってことは、署名はSHA2と楕円暗号を使ったSHA256withECDSA or SHA384withECDSA署名アルゴリズムしか使えないってことになります。

鍵共有や共通鍵暗号についてはパス検証においては関係ありません。

ハッシュ関数については署名以外で直接的に使うところはあまり無いのですが、以下はSuiteBじゃなくてもいいんでしょうか、、、
・KeyIdentifierを生成する際、SHA-1のみしか使えないのは仕様なので仕方ない
・OCSPの識別名のハッシュを使う際SHA-1のみしか使えないのも仕方ない
・パス構築でCRLやOCSPを取得する際にHTTPS、LDAPSであった場合
  TLSがSuiteBの暗号のみであることを必要とするのか?!
、、、んん〜〜っ、どうなんでしょうね、教えてエライ人(^^;


BSAFE Share for Javaではパス構築(CertPathBuilder)とパス検証(CertPathValidator)のために複数のアルゴリズムを提供しており、これは他のJCEプロバイダには無いところです。JCEアルゴリズム名の一覧は以下の通り。

・PKIX: RFC 3280に基づく証明書のパス構築・検証
・PKIX-SuiteB: 上記でSuiteBの暗号のみを使用
・PKIX-SuiteBTLS: TLS暗号化通信のパス構築・検証でSuiteBを使用
・X.509V1: X.509V1で規定されたパス構築・検証(証明書拡張が無い)

SuiteBかどうかは、パス検証後に署名アルゴリズムだけちゃちゃっとチェックすれば同じことのような気もしますが、JCEのアルゴリズムとして明示的に指定でき自動でやってくれちゃうっぽい、っていうのはスゴイところです。

SuiteBのTLSは?



暗号通信 TLS で SuiteB を使う時に、ハッシュやMacはまぁいいとして、共通鍵暗号として AES-GCM(Galois/Counter Mode)っていうのを使わなきゃいけないと思うんですが、BSAFEとか他のJCEプロバイダも含めてAES-GCMの対応ってどうなんでしょうか、、、単にAESのアルゴリズムパラメータで指定できるのかな、、、、

ここら辺は今後調査してみます。

BSAFE Share for Javaで足りないところ



BSAFE Share for Javaのサポートアルゴリズムの一覧をざっと見て思ったのは、RC2何とかやDES、TripleDESなんとかなど主要なものは一部ありますが、その派生のアルゴリズムでサポートしてないものが結構多いな、、、と感じました。後方互換性をバッサリ切っちゃうみたいなところはあるかと思います。

例えばこんなケースでサポートできなくて困らないかな、、、と心配します。

・PKCS#12形式の暗号化された秘密鍵
  古い暗号アルゴリズムが使われており、鍵が取り出せないなんてことが
  あるかもしれない。
・S/MIME暗号メールのメッセージ
  古い暗号アルゴリズムが使われていて、メッセージを復号して見られない
  なんてことがあるかも、、、

古いアルゴリズムのサポートに関してはIBMのJavaであるIBM J9に付いてくるJCEがかなり優秀だと思っていて古いものの扱いに困った時には、コレを使うといいと思います。S/MIMEは仕方ないですが、PKCS#12の方は、古いアルゴリズムが使われていたら新しい汎用的なアルゴリズムを使うように移行(マイグレーション)を考えておいた方がいいかもしれませんね。

というわけで、今回はBSAFE Share for JavaのSuiteB暗号スイート関連の話題を中心にサポートされている暗号アルゴリズムの紹介をしてみました。

ううむ、、、今回もマニアックな感じでしたね。すみません。

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

週末、RSA BSAFE Share for Javaをダウンロードしてみたので、いろいろ試してみました。

どんなアルゴリズムを実装しているか



JCEプロバイダの実装をゲットしたら、そのプロバイダがどんなアルゴリズムを実装しているのかを調べたくなるのが人情ってもの、、、、

製品紹介ページにアルゴリズムの一覧が書いてあったとしても、実際どうなの、、、とか、アルゴリズムの参照名がどうなっているの、、とかは気になりますし、それを知っとかないと後でアルゴリズム名が間違っているために障害起きたりして痛い目にあうので、日ごろからJCEプロバイダとアルゴリズム名の表はまめ手を入れて管理するようにしています。

現状手元にあるのが、
・IAIK JCE
・BouncyCastle
・Entrust Toolkit
・IBM J9 J2SE 1.6.0
そして新しく加わった
・RSA BSAFE Share

プロバイダ設定して、アルゴリズム一覧を出力するプログラムを使ってリストを出し、Excelで地味に表を更新します。

表を公開できればなぁ、、、とも思うんですが、26列×436行というHTMLでテーブル書くにはデカすぎるので、ちょっと出し方を考えておきます。

JCEプロバイダ毎にクセやら傾向があったりするんで、その辺りもいつかまとめて紹介したいと思います。

まず最初、プロバイダの設定



まず、2つのjarファイル、"lib/shareCrypto.jar"と"lib/shareTLS.jar"をJavaのクラスパスに入れてからRSA BSAFE ShareのJCEプロバイダを使えるようにするために方法が2つあります。

・設定ファイルに登録する方法
・動的に読み込む方法

利用できるJCEプロバイダは

$JAVA_HOME/jre/lib/security/java.security


のファイルの

security.provider.1=sun.security.provider.Sun
security.provider.2=sun.security.rsa.SunRsaSign
 :以下略


に登録しておけば使えます。番号が小さいほど優先して使われるので、

security.provider.1=com.rsa.jsafe.provider.JsafeJCE
security.provider.2=com.rsa.jsse.JsseProvider
 :以下略


のようにBSAFE Shareのものを先頭にもってきておき、他のは番号ずらしておけばBSAFEが優先して使われるようになります。

プログラムから動的にJCEプロバイダを使えるようにするにはプログラム中以下のような呼び出しをすれば、RSAのプロバイダが優先的に使えるようになります。

import java.security.Security;
import com.rsa.jsafe.provider.JsafeJCE;
import com.rsa.jsse.JsseProvider;

 :中略
try {
Security.insertProviderAt(new JsafeJCE(), 1);
Security.insertProviderAt(new JsseProvider(), 2);
} catch (Exception ex) { ex.printStackTrace(); }
 :中略


ここで注意しとかないといけないのが、JCEプロバイダ名とクラス名が合っていないところです。

・プロバイダ名:"JsafeJCE" (こっちはクラス名と一致で安心。暗号関連の実装)
・プロバイダ名:"RsaJsse" (こっちは違うので注意。SSLのためのプロバイダ)

例えばプロバイダを明示的に指定してSignatureオブジェクトをゲットしたい場合には、

import java.security.Signature;
Signature sig = Signature.getInstance("SHA1withRSA", "JsafeJCE");


先ほどRSA BSAFEを優先して登録したはずなので

Signature sig = Signature.getInstance("SHA1withRSA");


とプロバイダ指定しなくてもRSA BSAFEが使われます。

それでは、"JsafeJCE"プロバイダの"RIPEMD160"ハッシュアルゴリズムを使って文字列"aaa"に対するハッシュ値を計算してみましょう。

import java.math.*;
import java.security.*;
import com.rsa.jsafe.provider.*;
public class RipeTest1 {
public static void main(String[] args) {
try {
MessageDigest md = MessageDigest.getInstance("RIPEMD160", "JsafeJCE");
byte[] hash = md.digest("aaa".getBytes());
System.out.println(new BigInteger(hash).toString(16));
} catch (Exception ex) { ex.printStackTrace(); }
}
}


ちなみにRSA BSAFE Share for Javaは JDK 1.4.x では使えないみたいで、

実行しようとすると

Exception in thread "main" java.lang.UnsupportedClassVersionError: RipeTest1 (Unsupported major.minor version 50.0)


コンパイルしようとすると

RipeTest1.java:7: com.rsa.jsafe.provider.JsafeJCE にアクセスできません。クラスファイル shareCrypto.jar(JsafeJCE.class) は不正です。
クラスファイルのバージョン 49.0 は不正です。48.0 であるべきです。
削除するか、クラスパスの正しいサブディレクトリにあるかを確認してください。


のようなエラーが出てしまいます。

今日は、初回なんでこの辺で、、、、

次回はRSA BSAFE Share for Javaの特徴みたいなところを紹介できれば、、、、と思っています。

今更ながらNIST PKITS

NIST PKITS(PKI Test Suite) Path Validation Testing Programとは米国標準技術局(NIST:National Institute of Standards and Technology)で2004年に開発された、RFC 3280で規定された証明書のパス検証を正しく実装されているか確認できるテストケースのセットです。

ドキュメント、テスト用の証明書・CRL・リポジトリ(LDAPサーバー)・CMS署名データが公開されているので、誰でもダウンロードして試すことができます。量的には、






テストケース数だいたい250
テスト可能な証明書チェーン数224
証明書の数405
CRLの数175


というなかなか、ものすごいもので、こんなの手で一つ一つやっていたら日が暮れちゃうぐらいやり甲斐がのあるテストです。

Java系の実装でApache Ant+JUnitベースでテストしたい



Apache Antは言わずと知れたmakeコマンドの代わりとなるコンパイルやビルドなどしてくれる有難いツールです。JUnitはJavaの単体テストのフレームワークですね。

Challenge PKI Test Suiteでは、テストをゴッソリ動かすようなPerlスクリプトを作って動かしていましたが、既存のNIST PKITSで使おうとすると証明書やCRLなどテストデータをいちいちデータベースに投入する必要があってかなり面倒でした。

パス検証クライアントがJavaベースの時にAnt+JUnitの組み合わせでもう少しシンプルに、PKITSのディレクトリ構成を生かしながらテストできないかな、、、と考えていました。前のChallenge PKIが古いNISTのテストケースを参照にしていたので、新しい(といっても2004年ですが)テストケース、ファイル構成に対応させたいと思っていました。

SunのCertPathValidatorでちょっと気になっていたこともあったりしたので、昨日ちょっと時間があったので作ってみました。

実現方法



基本は、「PKITSをAnt+JUnitでパパッとやってレポート作成もそちらにお任せしたい」ってことです。テストの実施は、以下のような感じのテストケースの設定ファイルをJavaのpropertiesファイルで作ってテストさせます。設定ファイルには検証対象の証明書のチェーンと、CRLのファイル名が設定されています。

# t040101_ValidSignaturesTest1.cfg
path.crt.1=TrustAnchorRootCertificate.crt
path.crt.2=GoodCACert.crt
path.crt.3=ValidCertificatePathTest1EE.crt
path.crl.1=TrustAnchorRootCRL.crl
path.crl.2=GoodCACRL.crl


検証パラメータも同じくデフォルトをプロパティファイルで与えます。テストケースファイルにも例外を書けるようにしておきます。

input.initial-policy-set=2.5.29.32.0
input.initial-explicit-policy=0
input.initial-policy-mapping-inhibit=0
input.initial-inhibit-any-policy=0


で、このテストケース設定ファイルを250近く手で作るのはえらく面倒なのでテストケース設計書のPDFから自動で作れないかと考えました。テストケース設定ファイルの自動抽出では以下のようなことをします。

・pdf2text を用い PKITS.pdf のテストケース部分(4章)をテキストにする
・改行の乱れなどスクリプトで自動修正する
・サブサブセクション名からテストケース名を取得
・その中の記述より検証対象の証明書・CRL名を取得
・テストケース設定ファイルを生成

テストケース設定ファイル群よりパス検証をJUnitテストにより行うJavaのコードを自動生成するようなスクリプトを作りました。

JUnit単体テストのクラス

package pkits.auto;

import java.util.*;
import junit.framework.TestCase;
import pkits.util.*;

↓4.1章 "Signature Verification Test"の節のテスト
public class T0401_SignatureVerificationTest extends PKITSTestCase {
 public T0401_SignatureVerificationTest(String name) { super(name); }

 protected void setUp() {}
 protected void tearDown() {}

 ↓4.1.1節 "Valid Signatures Test1" のテストを設定ファイルを指定し実行
 public void test_t040101_ValidSignaturesTest1() throws Exception {
  doPKITSTestCase("t040101_ValidSignaturesTest1");
 }
 ↑コレをテストケース数だけ
 :以下略
}


クラスの頭の部分を"T0401_"にしたり、テストメソッドを"test_t040101_"のように節番号を含むようにしておくと、テストケース結果のレポートが節の順にきれいに表示されるので良いと思います。

テストの期待値ですが、基本的にはテストケース設定ファイルの名前"*_Valid*"、"*_Invalid*" で有効、無効を判断します。但し、4.8節の証明書ポリシのテストなどでは、テストメソッドが"test_t04080101_AllCertificatesSamePolicyTest1"のようにValid/Invalidは書かれていなくて、一つのテストの中で1〜4のポリシ処理の条件を変えてテストするので書かれていないわけです。これは、面倒ですが手で設定ファイルを分けてテストケース中に期待値を書くようにしました。

# t040801_AllCertificatesSamePolicyTest1.cfg
expectValue=INVALID
input.initial-policy-set=2.16.840.1.101.3.2.1.48.2
input.initial-explicit-policy=1


でCertPathValidatorを実際に動かす全てのテストの抽象親クラスを作って作業は大体おしまいです。

実行してみると、、、、

ここまでお膳立てできれば後は "ant test" 一発で動かすだけ。

% ant test
Buildfile: build.xml
init:
prepare:
[echo] ----------- NIST PKITS Test Runner 0.9.1 [2009] --------
prepare-src:
prepare-resource:
compile:
test:
[junit] Running pkits.auto.T0401_SignatureVerificationTest
[junit] Tests run: 6, Failures: 0, Errors: 0, Time elapsed: 2.139 sec
:中略
[junit] Running pkits.auto.T0404_BasicCertificateRevocationTestsTest
[junit] Tests run: 21, Failures: 1, Errors: 0, Time elapsed: 1.921 sec
[junit] Test pkits.auto.T0404_BasicCertificateRevocationTestsTest FAILED
:中略
[junitreport] Processing reports\TESTS-TestSuites.xml to null507600500
[junitreport] Transform time: 2874ms
BUILD SUCCESSFUL
Total time: 45 seconds


pkits01pub



250のテストケースで11個失敗、つまり期待値と不一致となっています。

pkits02pub



失敗したのはどれもCRL関係のやつです。Sunの実装はIndirect CRLとかDelta CRLとかサポートしていないので、当然といえば当然、、、、

pkits03pub



サポートしていないDelta CRLのテスト結果のさらなる詳細はこんなの。
Invalidのテストケースで無効になった場合には大抵CertPathValidatorExceptionなんですが、期待値通り例外が発生した場合JUnitの単体テストとしては成功で、するとどういう理由で例外発生したからテスト成功だったのかを結果表で表示させることができないんですよね。これは、ちょっと困ったところ。

仕方なく、標準出力を見たりします。

以上、こんな感じでApache AntとJUnitでNIST PKITS Path Validation Testを動かすことができ、集計結果の表示についてもほぼ満足できるものが完成しました。パチパチ(^^v

いや〜〜、最初からXMLか何かでテストケース書いてくれれば苦労も少ないんすけどね、、、、

今回はJUnitはJUnit 3.8という古いのを使ったんですが、今はJUnitは4.5になっており、それよりもTestNGの方が注目されているそうです、、、、知らなかった、、、そろそろTestNGに切り替える時が来たのかも、、、、、

JUnit 4.xはテスト結果のErrorが無くなってSuccess/Failureだけになってしまったので、これはちょっと困ったもんです。TestNGもこれは同じっぽい???あとJUnit 4.xもTestNGもJava 1.5以降のアノテーション機能を使っているので、古いJava 1.4を使い続けなければならない場合にちょっと困っています。


Sun Java CertPathBuilderの例外のイケズ

あるデジタル証明書が信頼するルート証明書から辿って有効であるかどうか判定するために認証パス検証(Certification Path Validation)という処理を行います。

これは、HTTPSで保護されたサイトに接続する時やS/MIME署名メールを開く際に実は裏で行われているとっても重要な処理です。

Java ではCertPathBuilderCertPathValidatorというクラスがあり、仕組み的にはX.509公開鍵証明書に限らず汎用的に証明書のパスが検証できたり、いろいろな会社の実装したアルゴリズム、実装方法が選択できるようになっています。

Sun Java にはRFC 3280準拠のパス検証実装が含まれており、これに基づくX.509証明書のパス検証ができるようになっています。


CertPathBuilderのSunプロバイダのPKIXアルゴリズム
検証対象証明書、中間証明書、CRL、証明書やCRLを取得するLDAPディレクトリ、OCSPレスポンダの設定、トラストアンカとなるルート証明書群を指定することにより、自動的にパス構築と(狭義の)パス検証を同時に行うことにより検証対象証明書のパスが有効であるかを判定します。内部でCertPathValidatorの実装が使われているわけではありません。
CertPathValidatorのSunプロバイダのPKIXアルゴリズム
検証対象証明書からトラストアンカとなるルート証明書の一つ手前までの証明書のチェーン(CertPath)を与えることにより、(狭義の)パス検証を行います。


Sun PKIXのCertPathBuilderのパス構築をザックリ図にしたのが以下、、、

pathbuild02



SunプロバイダのCertPathBuilderのPKIXアルゴリズム実装なんですが、これが、またちょっとイケズな実装になっていてエラーメッセージというか例外処理がとても不親切なんです。

パスを構築するための証明書が不足していたり、CRLが取得できなかったり、失効していたり、期限切れだったり、鍵用途が間違っていたり、中間CA証明書にあるべき基本制約のcAフラグがTRUEでなかったりすると例外が発生しますが、どんな理由であっても全く同じ

sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target


という例外メッセージが表示されます。これじゃ何がエラー理由だったのかさっぱりわからない(^^;メッセージ "unable to find valid ..." でぐぐってみると皆さんエラー理由がわからず困っておられる様子、、、、

CertPathBuilderExceptionクラスではgetCause()メソッドにより、その例外が発生した元の原因となる例外を取得できるんですが、Sunの実装では深さ優先探索のせいか?!不親切なのか?!これが設定されていないため、パス構築・検証に失敗した原因がわからないんです。

パスの深さ優先探索とは


SunのCertPathBuilderのPKIXアルゴリズムの実装ではパスを見つける際、幅優先探索と深さ優先探索のうち深さ優先探索が使われています。(人工知能系の方、Prolog系の方はよくご存知、、、(^^;)

以下のような少し複雑なPKIモデルで考えて見ましょう。

cpb01



ここでは信頼するルート証明書からエンドエンティティ証明書まで3通りのパスがあるんですが、そのうち2つは無効なパスになっています。

・ROOT→AAAのCA証明書は証明書有効期限が切れているので無効
・ROOT→BBBのCA証明書は基本制約にcA=TRUEが無いため無効

SunのCertPathBuilderでパス構築した場合の、パス構築で辿ってみた順序を図説したのが以下です。

cpb02



Sun実装ではエンドエンティティ証明書から始めてトラストアンカであるルート証明書まで深さ優先探索でパス構築を試みます。深さ優先とは、ぶっちゃけちゃうと「行ける所まで行く」、
踏み出せばその一足が道となり、その一足が道となる。迷わず行けよ。行けばわかるさ。ありがとう!
的なアントニオ猪木の名言の様なアルゴリズムです。

(1) エンドエンティティ証明書HHH←GGGからパス構築開始
(2) CA証明書 GGG←EEE、EEE←BBB を辿る。
   その際に都度、個々の追加される証明書に対して署名値、
   識別名前の一致、鍵用途、各種制約の処理失効検証などを行う。
(3) BBB←ROOT証明書の基本制約拡張にcA=TRUEが無いのでGGGに戻る
(4) CA証明書 GGG←DDD、DDD←AAA を辿る。
(5) BBB←ROOT証明書は期限切れなのでGGGに戻る。
(6) CA証明書 GGG←FFF、FFF←CCC、CCC←ROOT を辿る。
(7) トラストアンカまで辿れたので(6)が有効なパスとなり
   パス構築・検証は完了。

幅優先の場合、途中選択肢を全て抱えたまま探索するので多くメモリを消費すると言われています。そうした意味では深さ優先はまぁ、妥当かなと、、、

Sun PKIX CertPathBuilder実装の例外のイケズ



ただ、次のようなケースの場合、パス構築を試みて結局は有効なパスが見つからないため「unable to find valid certification path to requested target(要求された対象(証明書)に対する有効な認証パスが見つかりません)」ということになります。

cpb03



この際のCertPathBuilderException例外のgetCause()には何も入っていないので、何故パス構築・検証に失敗したのかは(普通は)謎のままです。せめてパス構築失敗の元を最後に起こした例外がgetCause()に設定されていれば結構理由がわかるもんなんですけど、残念ながらそうなってません。深さ優先探索なので「前の例外なんかいちいち保存しておくかぁ!!」という実装なんでしょう。

最後に発生した例外をgetCause()に設定するだけだと下のようなケースでは本質的な問題がわからないケースもあるんですが、まぁ、それは現状の何も無いよりはましみたいな感じで残しておいて欲しいなぁ、、、、

cpb04



パス構築で辿った順序が何故わかるか?



では、何故上の方の図でSun PKIX CertPathBuilderが辿ったパス構築順序がわかったかというとログを見たからなんです。CertPathBuilderやCertPathValidatorでは、以下のように "-Djava.security.debug=certpath"のオプションを付ければデバッグログが標準出力に表示されます。
% java -Djava.security.debug=certpath [CertPathを使ったJavaプログラム]


ただ、ここから出てくるログは今まで見てきたログの中では「中の下」ぐらいのダメさ加減で、多分観てもうんざりするだけだと思います。

で、パス構築で辿った順を観るには以下ようなdepthFirstSearchForwardの部分を見ればよいです。


certpath: SunCertPathBuilder.depthFirstSearchForward(CN=HHH, C=JP, State [
issuerDN of last cert: null
traversedCACerts: 0
init: true
keyParamsNeeded: false
subjectNamesTraversed:
[]]
)
・・・中略・・・
certpath: SunCertPathBuilder.depthFirstSearchForward(CN=ROOT, C=JP, State [
issuerDN of last cert: CN=ROOT, C=JP
traversedCACerts: 3
init: false
keyParamsNeeded: false
subjectNamesTraversed:
[CN=GGG, C=JP, CN=HHH, C=JP, CN=CCC, C=JP, CN=FFF, C=JP]]
)


これを観ていくとHHH→GGG→EEE→BBB×後戻り→DDD→AAA×後戻り→FFF→CCC→ROOT○と二度の失敗にもめげずに辿ったんだぁなぁ、、、頑張ったなぁ、、、、よしよし、、、という風に感慨深いものがあります。

ログを観てもAAA←ROOTやBBB←ROOTの証明書で基本制限に違反しているからとか、期限切れだからとか、そうした理由はログには全く書かれていません。

3本パスがあるうち何故その順序で選んだか?



HHH→GGGと辿って、次にEEE、DDD、FFFのどれを選んでも良いような気がしますが、必ずEEE、DDD、FFFの順序になりました。

証明書を探す際の入れ物としてArrayListをベースにしたCollectionCertStoreを使っていたんですが、そこに中間証明書を加える順序に依存しているのかもしれません。

結局 CertPathBuilder が出す例外は全く役に立たない



というわけで、Sun PKIX CertPathBuilderの吐く例外やログはというのは、どのような理由でパス構築・検証の失敗したのかわからないため使い物にならず、、ただ「unable to find valid certification path to requested target」の例外メッセージが空しくも得られるだけということがおわかり頂けたかと思います。

ではパス構築・検証がダメ理由を知りたきゃどうするか?



SSLサーバー認証では多くの場合SSLサーバー証明書・必要な中間CA証明書・ルート証明書がチェーンの形でごっそり送られてきますし、S/MIME署名メールやCAdES/XAdES長期署名の場合なんかも検証すべき証明書チェーンが概ねわかっている時があります。

自分で証明書チェーンを
・主体者・発行者の名前の一致で
・署名値の一致で
作ることもできることがあります。

そんなときは、自分で簡易パス構築で証明書チェーン作ってCertPathオブジェクトを生成し、CertPathValidatorで(狭義の)パス検証だけを行うのがいいように思います。

例えば、今回のケースで言えば

自前でパス構築だけして、パス検証はCertPathValidatorを使えば、

HHH→GGG→DDD→AAA→ROOTのパスの期限切れエラーなら
×失敗: timestamp check failed
×失敗理由詳細: NotAfter: Sun Jan 02 09:00:00 JST 2000


HHH→GGG→EEE→BBB→ROOTのパスの基本制約のcA=TRUE不足エラーなら
×失敗: basic constraints check failed: this is not a CA certificate


と、例外CertPathValidatorExceptionのgetMessage()で理由がわかってスッキリします。

今回はこれまたマニアックなネタでごめんなさいね。

自分でもパス構築してみたくなったら今回の証明書セットはこちらからダウンロードできます。

<参考リンク>
Sun J2SE 6 - Java PKI APIプログラマーズガイド
Sun J2SE 6 - Java暗号化アーキテクチャー(JCA) リファレンスガイド
Sun J2SE 6 - java.security.certパッケージ
IPA: 電子政府情報セキュリティ相互運用支援技術の開発
 ・GPKIアプリケーション実装ガイド報告書 (PDF 831KB)
   5章:Javaによる証明書パス構築・パス検証の実装の説明

@ITのJavaコード例一覧

Java SE コアAPI 使用コード例一覧 - @IT
このページは、Java開発者/プログラマのための、Java SE(旧、J2SE)のコアAPIの使用コード例の記事へのリンク集です。パッケージごとに表でまとめてあります。メソッドやコンストラクタ、例外などAPIの使い方の参考にしてください。パッケージやクラスの並び順は、標準のJavaDoc(「Java Platform, Standard Edition API 仕様」)と同様にしています。


過去に@ITで扱ったJavaプログラム例付き記事の一覧だそうです。ちょっと有難いかも、、、、

java.securityパッケージはMessageDigestしか例がありませんね(^^;

Java SignatureクラスのinitVerifyの問題(その3)

自堕落な技術者の日記 : Java SignatureクラスのinitVerifyの問題(その2) - livedoor Blog(ブログ)
initVerify(Certificate)でエラーが起きるのが、デフォルトで使われがちなSunRsaSign、SunJSSEなどのJCEプロバイダに固有の問題なのかプロバイダに共通の話なのか、ちょっと調べてみました。


java.security.Signature.initVerify(Certificate)メソッドを覗いてみるに

initVerify(Certificate)
 if (X509証明書でクリティカルフラグのあるkeyUsage拡張がある場合)
  - 証明書のkeyUsageの各ビットを
   X509Certificate().getKeyUsage()メソッドでブール値配列として取得
  - 1番目の配列要素がtrueでない、
   即ちdigitalSignatureビットが立っていないなら例外発生
 initVerify(PublicKey)と同じ処理


みたいな感じで共通のメソッドになっており、署名アルゴリズムやプロバイダには依存しないような形になっているようで、通りでプロバイダや署名アルゴリズムを変えてもnonRepudiationビットだけの証明書は弾かれるわけです。

逆に、initVerify(Certificate)では、これ以外のチェックは一切やっていないわけで、意外にシンプルだったのね、、、、って感じですね、、、、

Java SignatureクラスのinitVerifyの問題(その2)

自堕落な技術者の日記 : Java SignatureクラスのinitVerifyの問題 - livedoor Blog(ブログ)
どうもdigitalSignatureのkeyUsageビットが立っていないとエラーになるようなんです。


今日はinitVerify問題の続きを書きます。

X.509公開鍵証明書にはkeyUsage拡張というものがあって、署名用、暗号用、証明書の発行用など鍵をどんな目的で使ってよいのかを目的のビットを立てることにより定めることができます。

RFC 3280 4.2.1.3 Key Usage より
KeyUsage ::= BIT STRING {
digitalSignature (0),
nonRepudiation (1),
keyEncipherment (2),
dataEncipherment (3),
keyAgreement (4),
keyCertSign (5),
cRLSign (6),
encipherOnly (7),
decipherOnly (8) }


証明書やCRL発行以外のデータへのデジタル署名用としては digitalSignatureとnonRepudiationを両方か片一方か使うことができるのですがざっくりとした使い分けはこんな感じ。


  • digitalSignature(デジタル署名)ビット

    署名や認証など広い意味での署名のために使われます。ドキュメント、データ、プログラム(コードサイニング)への署名はもちろん、SSLサーバー証明書、SSLクライアント認証など幅広く使われます。証明書やCRLの発行のための署名には別のビット(keyCertSign,cRLSign)を使います。

  • nonRepudiation(否認防止)ビット

    電子文書による契約や申請など、一般に自然人が意思をもって署名を行い、後になって私は「こんな文書に署名などしていない。身に覚えがありません。」などと言い逃れできないようにする(=否認防止)ための署名のために使われます。自分に成りすまさて悪いことされないように鍵の管理は厳重にしなければなりません。鍵を使う前にはICカードを挿す、PINコードを打つなど操作確認のための能動的な行為がある方が良いですし、自動的に行われる署名にはできれば使わない方がいいかもしれません。



Java JCEを使って署名検証を行うためのコードはJythonで書くとざっくりこんな感じ。

cert = CertificateFactory.getInstance("X.509").\
generateCertificate(FileInputStream("aaa.cer"))

sig = Signature.getInstance("SHA1withRSA", "SunRsaSign")
sig.initVerify(cert)
sig.update(署名対象byte配列)
print "署名検証結果:", sig.verify(署名値byte配列)


今回問題が起きたのは、JavaのSignatureクラスで署名検証のためにnonRepudiationビットのみしか立っていない証明書を使って初期化(initVerify)した場合に"Wrong key usage"という例外により初期化エラーになるというものです。

JCEプロバイダによる違いはあるのか?



initVerify(Certificate)でエラーが起きるのが、デフォルトで使われがちなSunRsaSign、SunJSSEなどのJCEプロバイダに固有の問題なのかプロバイダに共通の話なのか、ちょっと調べてみました。















JCEプロバイダアルゴリズム結果
SunRsaSign 1.5SHA1withRSA×
SunJSSE 1.6SHA1withRSA×
SunMSCAPI 1.6SHA1withRSA×
BC 1.36SHA1withRSA×
BC 1.36SHA1WithRSA/ISO9796-2×
BC 1.36SHA1withRSA/PSS×
IAIK 3.142SHA/RSA×
IAIK 3.142SHA/RSA-ISO9796-2×
IAIK 3.142SHA1withRSAandMGF1×
Entrust 7.2SHA/RSA×
Entrust 7.2RSA-PSS×
IBMJCE 1.2SHA1withRSA×


全部きれいに「×」でdigitalSignatureビットが無くnonRepudiationビットしか立っていないような署名署名者証明書の場合には Signature.initVerify(Certificate) で初期化した場合に必ずエラーになることがわかりました。

実装側での問題回避策


まぁ、当たり前ですが回避策として初期化には証明書を使わずに証明書から公開鍵を取り出して使うという方法があります。

sig = Signature.getInstance("SHA1withRSA")
sig.initVerify(cert.getPublicKey())
sig.update(署名対象byte配列)
print "署名検証結果:", sig.verify(署名値byte配列)


証明書プロファイル



RFC 3280では一般的な署名やメールの署名などでは digitalSignature and/or nonRepudiationのビットを立てると書いてあるんですが、少なくともJavaではnonRepudiationビットだけであると検証エラーになってしまうので証明書プロファイルを決める際に注意しなければいけません。

RFC 3280 4.2.1.3 Key Usage
For example, when an RSA key should be used only to verify signatures on objects other than public key certificates and CRLs, the digitalSignature and/or nonRepudiation bits would be asserted.


相互運用性のためにはnonRepudiationビットだけを立てた証明書を発行するのは止め、digitalSignatureとnonRepudiationビットの双方を立てることで否認防止用証明書と解釈した方が良いかもしれません。

今日の教訓



結局のところnonRepudiationしか立っていない証明書もらっちゃったら「気をつけましょ、、、」ってことで、、、、(^^;




Java SignatureクラスのinitVerifyの問題

スペインからCAdES-BES署名が送られてきたので検証してみました。すると、ウムム、エラーです。

調べてみると

・署名に使った署名者の証明書のkeyUsageにはdigitalSignatureは無く
 nonRepudiationしかない

Signature.initVerify(cert)で上記の検証用証明書を設定すると
 「Wrong key usage」のメッセージと共に例外が発生

てなところのようです。使われているJCEプロバイダはSunJSSEでした。
どうもdigitalSignatureのkeyUsageビットが立っていないと
エラーになるようなんです。
今回は仕方ないのでSignature.initVerify(pubkey)で公開鍵を設定
することにより初期化するようにしました。

時間があるときに他のJCEプロバイダならどうなのか
調べてみようと思います。

署名に使った証明書はスペインの国民IDのスマートカードによるものだそうで適格証明書(Qualified Certificate)みたいです。実物で本物のbiometricInfo拡張のある証明書をゲットできてかなりうれしい、、、、、スペイン独自のpersonalDataInfo. (2.16.724.1.2.2.4.1)拡張なんかもあったりします。

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

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