自堕落な技術者の日記 : Java SignatureクラスのinitVerifyの問題 - livedoor Blog(ブログ)
今日はinitVerify問題の続きを書きます。
X.509公開鍵証明書にはkeyUsage拡張というものがあって、署名用、暗号用、証明書の発行用など鍵をどんな目的で使ってよいのかを目的のビットを立てることにより定めることができます。
証明書やCRL発行以外のデータへのデジタル署名用としては digitalSignatureとnonRepudiationを両方か片一方か使うことができるのですがざっくりとした使い分けはこんな感じ。
Java JCEを使って署名検証を行うためのコードはJythonで書くとざっくりこんな感じ。
今回問題が起きたのは、JavaのSignatureクラスで署名検証のためにnonRepudiationビットのみしか立っていない証明書を使って初期化(initVerify)した場合に"Wrong key usage"という例外により初期化エラーになるというものです。
initVerify(Certificate)でエラーが起きるのが、デフォルトで使われがちなSunRsaSign、SunJSSEなどのJCEプロバイダに固有の問題なのかプロバイダに共通の話なのか、ちょっと調べてみました。
全部きれいに「×」でdigitalSignatureビットが無くnonRepudiationビットしか立っていないような署名署名者証明書の場合には Signature.initVerify(Certificate) で初期化した場合に必ずエラーになることがわかりました。
まぁ、当たり前ですが回避策として初期化には証明書を使わずに証明書から公開鍵を取り出して使うという方法があります。
RFC 3280では一般的な署名やメールの署名などでは digitalSignature and/or nonRepudiationのビットを立てると書いてあるんですが、少なくともJavaではnonRepudiationビットだけであると検証エラーになってしまうので証明書プロファイルを決める際に注意しなければいけません。
相互運用性のためにはnonRepudiationビットだけを立てた証明書を発行するのは止め、digitalSignatureとnonRepudiationビットの双方を立てることで否認防止用証明書と解釈した方が良いかもしれません。
結局のところnonRepudiationしか立っていない証明書もらっちゃったら「気をつけましょ、、、」ってことで、、、、(^^;
どうも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.5 | SHA1withRSA | × |
| SunJSSE 1.6 | SHA1withRSA | × |
| SunMSCAPI 1.6 | SHA1withRSA | × |
| BC 1.36 | SHA1withRSA | × |
| BC 1.36 | SHA1WithRSA/ISO9796-2 | × |
| BC 1.36 | SHA1withRSA/PSS | × |
| IAIK 3.142 | SHA/RSA | × |
| IAIK 3.142 | SHA/RSA-ISO9796-2 | × |
| IAIK 3.142 | SHA1withRSAandMGF1 | × |
| Entrust 7.2 | SHA/RSA | × |
| Entrust 7.2 | RSA-PSS | × |
| IBMJCE 1.2 | SHA1withRSA | × |
全部きれいに「×」で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しか立っていない証明書もらっちゃったら「気をつけましょ、、、」ってことで、、、、(^^;