JDK 8 PKCS#11 リファレンス・ガイド


1.0 はじめに

2.0 Sun PKCS#11プロバイダ

2.1 要件
2.2 構成
2.3 Network Security Services (NSS)へのアクセス
2.4 PKCS#11のトラブルシューティング
2.5 PKCS#11の無効化
2.5.1 PKCS#11プロバイダの無効化
2.5.2 特定のメカニズムの無効化

3.0 アプリケーション開発者

3.1 トークン・ログイン
3.2 トークン鍵
3.3 プロバイダの遅延選択
3.4 JAAS KeyStoreLoginModule
3.5 JSSEキーストアおよびトラスト・ストアとしてのトークン

4.0 ツール

4.1 KeyToolおよびJarSigner
4.2 PolicyTool

5.0 プロバイダ開発者

5.1 プロバイダ・サービス
5.1.1 エンジン・クラスのインスタンス化
5.1.2 パラメータのサポート

付録A Sun PKCS#11プロバイダでサポートされるアルゴリズム

付録B Sun PKCS#11プロバイダのKeyStore要件

付録C プロバイダの例


1.0 はじめに

Javaプラットフォームでは、暗号化操作を実行するための一連のプログラミング・インタフェースを定義しています。これらのインタフェースは、総称してJava暗号化アーキテクチャ(JCA)およびJava暗号化拡張機能(JCE)と呼ばれています。仕様は、Java SEセキュリティ・ドキュメントのページから入手できます。

暗号化インタフェースはプロバイダ・ベースです。具体的には、アプリケーションはアプリケーション・プログラミング・インタフェース(API)とやり取りをし、実際の暗号化操作は、一連のサービス・プロバイダ・インタフェース(SPI)に従った構成済みプロバイダ内で実行されます。このアーキテクチャでは、プロバイダのさまざまな実装をサポートしています。ソフトウェアで暗号化操作を行うプロバイダもあれば、スマートカード・デバイスやハードウェア暗号化アクセラレータなどのハードウェア・トークン上で暗号化操作を行うプロバイダもあります。

暗号化トークン・インタフェース標準であるPKCS#11は、RSA Securityが策定し、ハードウェア暗号化アクセラレータやスマート・カードなどの暗号化トークンに対するネイティブ・プログラミング・インタフェースを定義しています。ネイティブPKCS#11トークンをJavaプラットフォームに簡単に統合するために、新しい暗号化プロバイダであるSun PKCS#11プロバイダがJ2SE 5.0リリースに導入されました。この新しいプロバイダでは、既存のJCAおよびJCE API対応アプリケーションがネイティブPKCS#11トークンにアクセスできます。アプリケーションへの変更は必要ありません。プロバイダの適切な構成をJava Runtimeに行うだけで済みます。

アプリケーションは既存のAPIを使用してPKCS#11機能のほとんどを利用できますが、柔軟性や機能性をさらに必要とするアプリケーションもあります。たとえば動的に抜き差しするスマート・カードをアプリケーションでもっと簡単に扱えるようにしたい場合があります。また、PKCS#11トークンで鍵とは関係ない一部の操作を認証する必要があるため、アプリケーションはキーストアを使用せずにトークンにログインできるようにする必要があります。J2SE 5.0ではJCAが拡張され、アプリケーションがさまざまなプロバイダを扱う際の柔軟性が向上しました。

このドキュメントでは、ネイティブPKCS#11トークンをJavaアプリケーションで使用できるように、Javaプラットフォームに構成する方法を説明します。また、PKCS#11プロバイダを始めとする各種のプロバイダをアプリケーションで扱いやすくするために、JCAに加えられた機能の向上点についても説明します。

2.0 Sun PKCS#11プロバイダ

その他の多くのプロバイダとは異なり、Sun PKCS#11プロバイダ自身は暗号化アルゴリズムを実装していません。その代わりに、Java JCAおよびJCE APIとネイティブPKCS#11暗号化APIとの間のブリッジとして動作し、これらの間の呼び出しと規則を変換します。つまり、標準JCAおよびJCE APIを呼び出すJavaアプリケーションなら、アプリケーションを変更しなくても、基盤となる次のようなPKCS#11実装で提供されるアルゴリズムを利用できるということです。

Java SEはネイティブPKCS#11実装にアクセスできるだけですが、それ自身にはネイティブPKCS#11実装が含まれていません。ただし、スマート・カードやハードウェア・アクセラレータなどの暗号化デバイスには、PKCS#11実装を含んだソフトウェアが付属していることが一般的で、製造元の指示に従ってインストールし、構成する必要があります。

2.1 要件

Sun PKCS#11プロバイダは、Solaris (SPARCとx86)、Linux (x86)およびWindowsプラットフォームの32ビットと64ビットの両方のJavaプロセスでサポートされています。

Sun PKCS#11プロバイダでは、PKCS#11 v2.0以降の実装がシステムにインストールされていなければなりません。この実装は、共有オブジェクト・ライブラリ(SolarisおよびLinuxでの.so)またはダイナミック・リンク・ライブラリ(Windowsでの.dll)の形態である必要があります。使用する暗号化デバイスにこのようなPKCS#11実装が含まれているかどうかを調べる方法、実装を構成する方法、およびライブラリ・ファイルのファイル名については、ベンダーが提供するマニュアルを参照してください。

Sun PKCS#11プロバイダでは、基盤となるPKCS#11実装で提供されるかぎり、多くのアルゴリズムをサポートしています。アルゴリズムとそれらに対応するPKCS#11メカニズムを、付録Aの表に示します。

2.2 構成

Sun PKCS#11プロバイダは、メイン・クラスsun.security.pkcs11.SunPKCS11で実装されており、構成ファイルのフル・パス名を引数として使用できます。プロバイダを使用するには、あらかじめJava暗号化アーキテクチャ (JCA)を使用してインストールしておく必要があります。あらゆるJCAプロバイダと同様に、このプロバイダのインストールは静的またはプログラミングで実行できます。このプロバイダを静的にインストールするには、Javaセキュリティのプロパティ・ファイル($JAVA_HOME/lib/security/java.security)にプロバイダを追加します。たとえば次は、Sun PKCS#11プロバイダを構成ファイル/opt/bar/cfg/pkcs11.cfgとともにインストールするjava.securityファイルの一部です。
# configuration for security providers 1-6 omitted
security.provider.7=sun.security.pkcs11.SunPKCS11 /opt/bar/cfg/pkcs11.cfg
このプロバイダを動的にインストールするには、適切な構成ファイル名を使用してプロバイダのインスタンスを作成し、インストールします。次に例を示します。
String configName = "/opt/bar/cfg/pkcs11.cfg";
Provider p = new sun.security.pkcs11.SunPKCS11(configName);
Security.addProvider(p);

PKCS#11実装あたり複数のスロットを使用する場合や、複数のPKCS#11実装を使用する場合は、適切な構成ファイルでそれぞれのインストールを繰り返すだけです。これにより、それぞれのPKCS#11実装の各スロットに対してSun PKCS#11プロバイダのインスタンスが1つ作成されることになります。

構成ファイルはテキスト・ファイルで、次の形式のエントリが含まれます。

attribute = valueattributevalueの有効な値については、このセクションの表で説明しています。2つある必須属性は、nameおよびlibraryです。次に、構成ファイルの例を示します。
name = FooAccelerator
library = /opt/foo/lib/libpkcs11.so
コメントは、# (シャープ)記号で始まる行に記述します。

属性 説明
library PKCS#11実装のパス名 PKCS#11実装のフル・パス名(拡張子を含む)。パス名の書式はプラットフォームに依存します。たとえば、SolarisおよびLinuxではPKCS#11実装のパス名が/opt/foo/lib/libpkcs11.soなどになりますが、Windowsでのパス名はC:\foo\mypkcs11.dllなどになります。
name このプロバイダ・インスタンスの名前接尾辞 この文字列は、接頭辞SunPKCS11-と連結して、このプロバイダ・インスタンスの名前(つまり、プロバイダ・インスタンスのProvider.getName()メソッドで返される文字列)を生成します。たとえばname属性が「FooAccelerator」の場合、プロバイダ・インスタンスの名前は「SunPKCS11-FooAccelerator」になります。
description このプロバイダ・インスタンスの説明 この文字列は、プロバイダ・インスタンスのProvider.getInfo()メソッドで返されます。何も指定されていない場合、デフォルトの説明が返されます。
slot スロットのID このプロバイダ・インスタンスが関連付けされているスロットのID。たとえば、PKCS#11のID 1のスロットでは、1を使用します。指定できるのは、多くともslotslotListIndexのどちらか1つです。どちらも指定しない場合のデフォルトは、値0slotListIndexです。
slotListIndex スロットのインデックス このプロバイダ・インスタンスが関連付けされているスロットのインデックス。これは、PKCS#11の関数C_GetSlotListで返されるすべてのスロットのリストに対するインデックスです。たとえば、0はリストの先頭のスロットを表します。指定できるのは、多くともslotslotListIndexのどちらか1つです。どちらも指定しない場合のデフォルトは、値0slotListIndexです。
enabledMechanisms 有効にするPKCS#11メカニズムのリスト。空白文字で区切り、全体を中カッコ()で囲む これは、Sun PKCS#11プロバイダとPKCS#11トークンの両方でサポートされている場合に、このプロバイダ・インスタンスが使用すべきPKCS#11メカニズムのリストです。その他のメカニズムはすべて無視されます。リスト内の各エントリは、PKCS#11メカニズムの名前になります。2つのPKCS#11メカニズムから成るリストの例を次に示します。
enabledMechanisms = {
  CKM_RSA_PKCS
  CKM_RSA_PKCS_KEY_PAIR_GEN
}
指定できるのは、多くともenabledMechanismsdisabledMechanismsのどちらか1つです。どちらも指定しない場合、有効なメカニズムは、Sun PKCS#11プロバイダとPKCS#11トークンの両方でサポートされているメカニズムになります。
disabledMechanisms 無効にするPKCS#11メカニズムのリスト。空白文字で区切り、全体を中カッコ()で囲む このプロバイダ・インスタンスが無視するPKCS#11メカニズムのリスト。リストされたあらゆるメカニズムは、トークンとSun PKCS#11プロバイダでサポートされていても、プロバイダによって無視されます。これらのサービスを無効にするために文字列SecureRandomおよびKeyStoreを指定できます。

指定できるのは、多くともenabledMechanismsdisabledMechanismsのどちらか1つです。どちらも指定しない場合、有効なメカニズムは、Sun PKCS#11プロバイダとPKCS#11トークンの両方でサポートされているメカニズムになります。

attributes 下記参照 attributesオプションは、PKCS#11鍵オブジェクトの作成時に設定される追加のPKCS#11属性を指定するために使用します。これにより、特定の属性を必要とするトークンを使用できるようになります。詳細は、次のセクションを参照してください。

attributesの構成

attributesオプションは、PKCS#11鍵オブジェクトの作成時に設定される追加のPKCS#11属性を指定するために使用できます。デフォルトでSun PKCS#11プロバイダでは、オブジェクトの作成時に必須のPKCS#11属性を指定するだけです。たとえばRSA公開鍵では、鍵タイプおよびアルゴリズム(CKA_CLASSおよびCKA_KEY_TYPE)と、RSA公開鍵の鍵の値(CKA_MODULUSおよびCKA_PUBLIC_EXPONENT)を指定します。使用しているPKCS#11ライブラリでは、実装固有のデフォルト値をRSA公開鍵のその他の属性に割り当てます。たとえば、メッセージの暗号化と検証に鍵を使用できます(CKA_ENCRYPTおよびCKA_VERIFY = true)。

attributesオプションは、PKCS#11実装で割り当てたデフォルト値を使用しない場合や、PKCS#11実装でデフォルト値をサポートしないために、明示的に値を指定する必要がある場合に使用します。使用しているPKCS#11実装でサポートしない属性や、該当する鍵タイプで無効な属性を指定すると、実行時に操作が失敗する原因となります。

オプションは0回または複数回指定できます。また、次に説明するように、構成ファイルで指定した順番でオプションが処理されます。attributesオプションは次の書式です。

attributes(operation, keytype, keyalgorithm) = {
  name1 = value1
  [...]
}
operationの有効な値は次のとおりです。 keytypeの有効な値は、CKO_PUBLIC_KEYCKO_PRIVATE_KEY、およびCKO_SECRET_KEYで、それぞれ公開鍵、非公開鍵、および秘密鍵に対応しています。また、任意の鍵タイプに一致する*もあります。

keyalgorithmの有効な値は、PKCS#11仕様に定義されたCKK_xxx定数のいずれか1つ、または任意の鍵アルゴリズムに一致する*です。Sun PKCS11プロバイダで現在サポートしているアルゴリズムは、CKK_RSA、CKK_DSA、CKK_DH、CKK_AES、CKK_DES、CKK_DES3、CKK_RC4、CKK_BLOWFISH、およびCKK_GENERICです。

属性の名前と値は、1つまたは複数の名前と値のペアのリストとして指定されます。nameはPKCS#11仕様のCKA_xxx定数(CKA_SENSITIVEなど)でなければなりません。valueは、次のいずれかになります。

attributesオプションを複数回指定すると、エントリは、まとめられた属性で指定された順序で、あとの属性が前の属性をオーバーライドして処理されます。たとえば、次の構成ファイルを考えてみます。
attributes(*,CKO_PRIVATE_KEY,*) = {
  CKA_SIGN = true
}

attributes(*,CKO_PRIVATE_KEY,CKK_DH) = {
  CKA_SIGN = null
}

attributes(*,CKO_PRIVATE_KEY,CKK_RSA) = {
  CKA_DECRYPT = true
}
1番目のエントリでは、すべての非公開鍵に対してCKA_SIGN = trueを指定しています。2番目のオプションでは、Diffie-Hellman非公開鍵についてnullでオーバーライドするため、CKA_SIGN属性はDiffie-Hellman非公開鍵に対してまったく指定されません。最後に、3番目のオプションでは、RSA非公開鍵に対してCKA_DECRYPT = trueを指定しています。つまりRSA非公開鍵には、CKA_SIGN = trueCKA_DECRYPT = true両方のセットがあることになります。

attributesオプションには特殊な形式もあります。構成ファイルにattributes = compatibilityと書くことができます。これは、属性ステートメントのセット全体に対するショートカットです。これはプロバイダが既存のJavaアプリケーションとの互換性を最大限に保つことを目的としています。これにより、Javaアプリケーションでは、たとえばすべての鍵コンポーネントにアクセス可能で、秘密鍵を暗号化および復号化の両方に使用可能であることが期待されます。compatibility属性の行は、他のattributesの行とともに使用できます。この場合は先に説明したような、同様のアグリゲーションおよびオーバーライドの規則が適用されます。

2.3 Network Security Services (NSS)へのアクセス

Network Security Services (NSS)は、Mozilla/Firefoxブラウザ、SunのJava Enterprise Systemサーバー・ソフトウェアおよびその他の多くの製品で使用されるオープン・ソースの一連のセキュリティ・ライブラリです。暗号化APIはPKCS#11に基づいていますが、PKCS#11標準ではない特別な機能が含まれています。Sun PKCS#11プロバイダには、NSS固有の機能(複数のNSS固有の構成ディレクティブなど)と相互に作用するためのコードが含まれています。これらについては次で説明します。

最適な結果を得るために、使用可能な最新バージョンのNSSを使用することをお薦めします。少なくともバージョン3.12になります。

次に説明されているnss構成指示が使用されている場合、Sun PKCS#11プロバイダはNSS固有のコードを使用します。この場合、通常の構成コマンドlibraryslot、およびslotListIndexは使用できません。

属性 説明
nssLibraryDirectory NSSおよびNSPRライブラリを含むディレクトリ NSSおよびNSPRライブラリを含むディレクトリのフル・パス名。Java VMとして同じプロセス内で実行されている別のコンポーネントによってNSSがすでにロードおよび初期化されていないかぎり、この属性を指定する必要があります。

プラットフォームに応じて、このディレクトリを含めるためにLD_LIBRARY_PATHまたはPATH (Windows)を設定して、オペレーティング・システムで依存ライブラリを検索できるようにする必要がある場合があります。

nssSecmodDirectory NSS DBファイルを含むディレクトリ NSS構成および鍵情報(secmod.dbkey3.db、およびcert8.db)を含むディレクトリのフル・パス名。別のコンポーネントによってNSSがすでに初期化されていないか(上記参照)、または次に説明されているようにデータベース・ファイルなしでNSSが使用されないかぎり、この指示を指定する必要があります。
nssDbMode readWritereadOnlynoDbのいずれか この指示は、NSSデータベースへのアクセス方法を決定します。読書きモードではフル・アクセスが可能ですが、データベースには一度に1つのプロセスのみがアクセスする必要があります。読取り専用モードでは、このファイルの変更は許可されません。

noDbモードを使用すると、データベース・ファイルなしで純粋に暗号化プロバイダとしてNSSを使用できます。PKCS11 KeyStoreを使用して持続的な鍵を作成することはできません。

nssModule keystorecryptofipstrustanchorsのいずれか さまざまなライブラリやスロットを使用してNSSの機能が使用できるようになっています。この指示は、SunPKCS11のインスタンスがアクセスするモジュールを決定します。

cryptoモジュールは、noDbモードでのデフォルトです。このモジュールはログインを必要としない暗号化操作をサポートしますが、持続的な鍵はサポートしません。

FIPS-140準拠モードに対してNSSのsecmod.dbが設定されている場合、fipsモジュールがデフォルトになります。このモードでは、NSSによって、使用可能なアルゴリズムおよび鍵を作成する場合に使用するPKCS#11属性が制限されます。

keystoreモジュールは、その他の構成でのデフォルトです。このモジュールは、PKCS11 KeyStoreを使用して持続的な鍵をサポートします。この鍵はNSS DBファイルに保存されます。このモジュールではログインが必要です。

信頼できるアンカー・ライブラリを含むようにsecmod.dbが構成されている場合、trustanchorsモジュールを使用すると、PKCS11 KeyStoreを使用してNSSの信頼できるアンカー証明書にアクセスできます。

NSSのサンプルSunPKCS11構成ファイル

純粋な暗号化プロバイダとしてのNSS
name = NSScrypto
nssLibraryDirectory = /opt/tests/nss/lib
nssDbMode = noDb
attributes = compatibility
FIPS 140準拠の暗号化トークンとしてのNSS
name = NSSfips
nssLibraryDirectory = /opt/tests/nss/lib
nssSecmodDirectory = /opt/tests/nss/fipsdb
nssModule = fips

2.4 PKCS#11のトラブルシューティング

PKCS#11に問題が発生し、デバッグが必要になることがあります。ライブラリ、スロット、トークンおよびメカニズムのデバッグ情報を表示するには、$JAVA-HOME/jre/lib/security/sunpkcs11-solaris.cfgファイルにshowInfo=trueを追加します。

その他のデバッグ情報については、次のいずれかのオプションを指定してJavaプロセスを起動または再起動してください。

2.5 PKCS#11プロバイダまたは個々のPKCS#11メカニズム(あるいはその両方)の無効化

トラブルシューティング・プロセスの一環として、PKCS#11プロバイダ、または特定のプロバイダに固有のメカニズムを一時的に無効にすると便利な場合があります。これはあくまでも一時的な措置であることに注意してください。PKCS#11プロバイダを無効にすると、そのプロバイダは使用できなくなるため、アプリケーションが破損したり、アプリケーションのパフォーマンスに影響が出たりする可能性があります。問題が特定されたら、その固有のメカニズムのみを無効のままにしてください。

2.5.1 PKCS#11プロバイダの無効化

PKCS#11プロバイダは、次のいずれかの方法で無効にできます。

  1. 単一のJavaプロセスについてPKCS#11を無効化: 次のJavaコマンド行フラグを指定してJavaプロセスを起動または再起動します。
    -Dsun.security.pkcs11.enable-solaris=false
    

    注意:このステップは、デフォルトのSolaris PKCS11プロバイダ・ファイル(sun.security.pkcs11.SunPKCS11、/lib/security/sunpkcs11-solaris.cfg,、/usr/lib/libpkcs11.so)に基づくSunPKCS11プロバイダにのみ適用されます。
  2. 特定のJavaインストールで実行されるすべてのJavaプロセスについてPKCS#11を無効化: これは、java.security.Security.removeProvider() APIを使用して動的に実行するか(このドキュメントには記載されていません)、次に示すように$JAVA_HOME/jre/lib/security/java.securityファイルを編集してPKCS#11セキュリティ・プロバイダをコメント・アウトすることにより静的に実行することもできます(プロバイダの順序の番号を変更することを忘れないでください)。

    # List of providers and their preference orders (see above):
    security.provider.1=com.oracle.security.ucrypto.UcryptoProvider ${java.home}/lib/security/ucrypto-solaris.cfg
    #security.provider.2=sun.security.pkcs11.SunPKCS11 ${java.home}/lib/security/sunpkcs11-solaris.cfg
    security.provider.2=sun.security.provider.Sun
    security.provider.3=sun.security.rsa.SunRsaSign
    security.provider.4=sun.security.ec.SunEC
    security.provider.5=com.sun.net.ssl.internal.ssl.Provider
    security.provider.6=com.sun.crypto.provider.SunJCE
    ....................
    ....................
    

    このJavaのインストールで実行されているJavaプロセスを起動または再起動します。

2.5.2 特定のメカニズムの無効化

PKCS11のメカニズムのいずれかで問題が発生した場合、PKCS11プロバイダ全体ではなく、その特定のメカニズムのみを無効にすることにより、問題を解決できます(それまでにPKCS11プロバイダを無効にしていた場合は、再度有効にすることを忘れないでください)。たとえば、SecureRandomメカニズムのみを無効にするには、$JAVA_HOME/jre/lib/security/sunpkcs11-solaris.cfgファイルの無効なメカニズムのリストにSecureRandomを追加します。次に示すのは、そのようなファイルからの抜粋です。

$ more sunpkcs11-solaris.cfg
.......
........
........

disabledMechanisms = {

  SecureRandom

  CKM_MD2
  CKM_MD5
  CKM_SHA_1

.........
.........
}

注意: sunpkcs11-solaris.cfgファイルの前述の部分は、無効にするメカニズムを追加する場所を示す例であり、完全なファイルではありません。


3.0 アプリケーションの開発

Javaアプリケーションは、既存のJCAおよびJCE APIを使用して、Sun PKCS#11プロバイダ経由でPKCS#11トークンにアクセスできます。多くのアプリケーションではこれで十分ですが、抽出不可能な鍵や動的に変更されるスマート・カードといった一部のPKCS#11機能を扱うには難しい場合があります。そのため、一部のPKCS#11機能を使用するアプリケーションのサポートを向上できるように、多くの拡張機能がAPIに加えられました。このセクションでは、そのような拡張機能について説明します。

3.1 トークン・ログイン

秘密鍵へのアクセスなど一部のPKCS#11操作では、その操作の実行前に、PIN (個人識別番号)を使用してログインする必要があります。ログインが必要となる操作でもっとも多いのはトークン上の鍵を扱う操作です。Javaアプリケーションではそのような操作で、最初にキーストアをロードするのが一般的です。java.security.KeyStoreクラス経由でキーストアとしてPKCS#11トークンにアクセスするときは、loadメソッドに対してパスワード入力パラメータでPINを指定できます。これは、J2SE 5.0より前でアプリケーションがキーストアを初期化する方法に似ています。PINは、トークンにログインするために、Sun PKCS#11プロバイダが使用します。次に例を示します。

char[] pin = ...; 
KeyStore ks = KeyStore.getInstance("PKCS11");
ks.load(null, pin); 

この例は、静的なキーストアとしてPKCS#11トークンを扱うアプリケーションに適しています。抜き差しされるスマート・カードのようにPKCS#11トークンをより動的に利用したいアプリケーションの場合は、新しいKeyStore.Builderクラスを使用できます。コールバック・ハンドラでPKCS#11キーストアのビルダーを初期化する方法を次の例に示します。

KeyStore.CallbackHandlerProtection chp =
    new KeyStore.CallbackHandlerProtection(new MyGuiCallbackHandler());
KeyStore.Builder builder =
    KeyStore.Builder.newInstance("PKCS11", null, chp);

Sun PKCS#11プロバイダの場合、コールバック・ハンドラはPasswordCallbackを満たすことができなければなりません。PasswordCallbackは、ユーザーにPINを要求する場合に使用されます。アプリケーションがキーストアにアクセスしなければならない場合は、ビルダーを次のように使用します。

KeyStore ks = builder.getKeyStore();
Key key = ks.getKey(alias, null);

ビルダーは、先に構成されたコールバック・ハンドラで使用するパスワードを必要に応じてユーザーに求めます。ビルダーがパスワードを要求するのは、初回のアクセス時のみです。アプリケーションのユーザーが同じスマート・カードを使用し続ける場合は、もう一度パスワードを要求されることはありません。ユーザーがスマート・カードを抜いて、別のスマート・カードを差した場合は、新しいカードに対するパスワードを要求されます。

PKCS#11トークンによっては、鍵とは関係ない操作でもトークン・ログインを必要とする場合があります。そのような操作を使用するアプリケーションでは、新しく導入されたjava.security.AuthProviderクラスを使用できます。AuthProviderクラスは、java.security.Providerを拡張し、プロバイダでログインおよびログアウト操作を行なったり、プロバイダが使用するコールバック・ハンドラを設定したりするためのメソッドを定義しています。

Sun PKCS#11プロバイダの場合、コールバック・ハンドラはPasswordCallbackを満たすことができなければなりません。PasswordCallbackは、ユーザーにPINを要求する場合に使用されます。

次に、アプリケーションでAuthProviderを使用してトークンにログインする方法の例を示します。

AuthProvider aprov = (AuthProvider)Security.getProvider("SunPKCS11");
aprov.login(subject, new MyGuiCallbackHandler());

3.2 トークン鍵

JavaのKeyオブジェクトには、実際の鍵データが含まれる場合も含まれない場合もあります。

アプリケーションおよびプロバイダでは、各種のKeyオブジェクトを表すのに適切なインタフェースを使用する必要があります。ソフトウェアKeyオブジェクト(または実際の鍵データにアクセスできる任意のKeyオブジェクト)では、java.security.interfacesおよびjavax.crypto.interfacesパッケージにインタフェース(DSAPrivateKeyなど)を実装する必要があります。抽出不可能なトークン鍵を表すKeyオブジェクトでは、java.securityおよびjavax.cryptoパッケージに、関連するジェネリック・インタフェース(PrivateKeyPublicKey、またはSecretKey)だけを実装する必要があります。鍵アルゴリズムの特定は、Key.getAlgorithm()メソッドを使用して実行されます。

アプリケーションでは、抽出不可能なトークン鍵のKeyオブジェクトは、そのトークンに関連付けされたプロバイダだけが使用できることに注意しなければなりません。

3.3 プロバイダの遅延選択

J2SE 5.0より前では、Cipher.getInstance("AES")などのJava暗号化getInstance()メソッドは、要求されたアルゴリズムを実装している最初のプロバイダからの実装を返していました。これは、ソフトウェアKeyオブジェクトだけを使用できるプロバイダで、抽出不可能なトークン鍵のKeyオブジェクトをアプリケーションが使用しようとする場合に問題がありました。このような場合、プロバイダはInvalidKeyExceptionをスローします。これは、CipherKeyAgreementMacSignatureの各クラスの問題です。

J2SE 5.0では、関連する初期化メソッドが呼び出されるまでプロバイダの選択を遅らせることで、この問題に対応しています。初期化メソッドではKeyオブジェクトを受け入れ、指定したKeyオブジェクトを受け入れられるプロバイダをその時点で判断できます。これにより、選択したプロバイダで、指定したKeyオブジェクトを使用できることが保証されます。次に、影響のある初期化メソッドを示します。

また、アプリケーションが初期化メソッドを複数回呼び出すと(毎回異なる鍵を使用するなど)、その鍵に適切なプロバイダが毎回選択されます。つまり、初期化の呼び出しごとに異なるプロバイダが選択される可能性があります。

このプロバイダの遅延選択は、アプリケーションからは認識されませんが、CipherKeyAgreementMac、およびSignaturegetProvider()メソッドの動作に影響します。getProvider()が初期化操作の発生する (したがって、プロバイダ選択が起こる前)に呼び出された場合は、要求されるアルゴリズムがサポートされている最初のプロバイダが返されます。このプロバイダは、初期化メソッドが呼び出されたあとに選択されたプロバイダと同じにならない場合があります。getProvider()が初期化操作のあとに呼び出された場合は、実際に選択されたプロバイダが返されます。アプリケーションでは関連する初期化メソッドを呼び出したあとにのみ、getProvider()を呼び出すようにしてください。

getProvider()だけでなく、次のメソッドにも同様の影響があります。

3.4 JAAS KeyStoreLoginModule

Java SEには、JAASキーストア・ログイン・モジュールである KeyStoreLoginModuleが付属しています。このモジュールを使用すると、アプリケーションで特定のキーストア内のIDを使用して認証を行うことができます。アプリケーションは認証後に、自身のプリンシパルおよびクレデンシャル情報(証明書および秘密鍵)をキーストアから取得します。このログイン・モジュールを使用し、PKCS#11トークンをキーストアとして使用するように構成すると、アプリケーションはこの情報をPKCS#11トークンから取得できるようになります。

次のオプションを使用してKeyStoreLoginModuleを構成すると、PKCS#11トークンをキーストアとして使用できます。

ここで、some_pin_urlはPINの場所です。keyStorePasswordURLオプションを省略すると、ログイン・モジュールはPINをアプリケーションのコールバック・ハンドラから取得し、PasswordCallbackでそのPINを指定します。PKCS#11トークンをキーストアとして使用する構成ファイルの例を次に示します。
other {
    com.sun.security.auth.module.KeyStoreLoginModule required
        keyStoreURL="NONE"
  keyStoreType="PKCS11"
  keyStorePasswordURL="file:/home/joe/scpin";
};

複数のSun PKCS#11プロバイダを動的に構成した場合、またはjava.securityセキュリティ・プロパティ・ファイル内で構成した場合は、keyStoreProviderオプションを使用して、特定のプロバイダ・インスタンスを対象にします。このオプションの引数は、プロバイダの名前です。Sun PKCS#11プロバイダの場合、プロバイダ名はSunPKCS11-TokenNameという形式になります。ここで、TokenNameはプロバイダ・インスタンスが構成された名前の接尾辞です。詳細は、構成属性の表を参照してください。たとえば、次の構成ファイルでは、PKCS#11プロバイダ・インスタンスに名前接尾辞SmartCardで名前を付けています。

other {
    com.sun.security.auth.module.KeyStoreLoginModule required
        keyStoreURL="NONE"
  keyStoreType="PKCS11"
  keyStorePasswordURL="file:/home/joe/scpin"
  keyStoreProvider="SunPKCS11-SmartCard";
};

保護された認証パスを介してのログインをサポートしているPKCS#11トークンもあります。たとえばスマート・カードには、PINを入力するための専用のPINパッドがある場合があります。生体測定機器にも、認証情報を取得する専用の方法が用意されています。PKCS#11トークンに保護された認証パスがある場合は、protected=trueオプションを使用し、keyStorePasswordURLオプションは省略します。そのようなトークン用の構成ファイルの例を次に示します。

other {
    com.sun.security.auth.module.KeyStoreLoginModule required
        keyStoreURL="NONE"
  keyStoreType="PKCS11"
  protected=true;
};

3.5 JSSEキーストアおよびトラスト・ストアとしてのトークン

PKCS#11トークンをJSSEキーストアまたはトラスト・ストアとして使用できるように、JSSEアプリケーションでは先に説明した APIを使用してKeyStoreをインスタンス化します。このKeyStoreは、PKCS#11トークンによって戻され、キー・マネージャおよびトラスト・マネージャに渡されます。こうしてJSSEアプリケーションでは、トークン上の鍵にアクセスできるようになります。

JSSEでは、システム・プロパティ経由でキーストアおよびトラスト・ストアを使用するように構成できます(『JSSEリファレンス・ガイド』を参照)。PKCS#11トークンをキーストアまたはトラスト・ストアとして使用するには、javax.net.ssl.keyStoreTypeおよびjavax.net.ssl.trustStoreTypeシステム・プロパティをそれぞれ「PKCS11」に設定し、javax.net.ssl.keyStoreおよびjavax.net.ssl.trustStoreシステム・プロパティをそれぞれ「NONE」に設定します。特定のプロバイダ・インスタンスを使用するように指定するには、javax.net.ssl.keyStoreProviderおよびjavax.net.ssl.trustStoreProviderシステム・プロパティを使用します(例: 「SunPKCS11-SmartCard」)。

4.0 ツール

J2SE 5.0では、セキュリティ・ツールが更新され、新しいSun PKCS#11プロバイダを使用した操作がサポートされました。変更内容を次に説明します。

4.1 KeyToolおよびJarSigner

Sun PKCS#11プロバイダがJavaランタイムの$JAVA_HOME/lib/securityディレクトリ内にあるjava.securityセキュリティ・プロパティ・ファイルで構成されている場合、次のオプションを使用することで、PKCS#11トークンの操作にkeytoolおよびjarsignerを使用できます。

構成済みPKCS#11トークンの内容をリストするコマンドの例を次に示します。
keytool -keystore NONE -storetype PKCS11 -list
PINは -storepassオプションで指定できます。指定されない場合、keytoolおよびjarsignerでは、トークンPINを要求します。トークンに保護された認証パス(専用のPINパッドや生体読取り機など)がある場合、-protectedオプションを指定する必要がありますが、パスワード・オプションを指定する必要はありません。

複数のSun PKCS#11プロバイダをjava.securityセキュリティ・プロパティ・ファイル内で構成した場合は、-providerNameオプションを使用して、特定のプロバイダ・インスタンスを対象にします。このオプションの引数は、プロバイダの名前です。

Sun PKCS#11プロバイダの場合、providerNameSunPKCS11-TokenNameという形式になります。ここでTokenNameは、プロバイダ・インスタンスが構成された名前の接尾辞です。詳細は構成属性の表を参照してください。たとえば、次のコマンドでは、名前接尾辞SmartCardのPKCS#11キーストア・プロバイダ・インスタンスの内容をリストします。
keytool -keystore NONE -storetype PKCS11 \
        -providerName SunPKCS11-SmartCard \
        -list

Sun PKCS#11プロバイダをjava.securityセキュリティ・プロパティ・ファイル内で構成していない場合は、次のオプションを使用して、プロバイダを動的にインストールするようにkeytoolおよびjarsignerを設定します。

ここで、ConfigFilePathはトークン構成ファイルのパスです。次に、Sun PKCS#11プロバイダがjava.securityファイルで構成されていないときにPKCS#11キーストアをリストするコマンドの例を示します。
keytool -keystore NONE -storetype PKCS11 \
        -providerClass sun.security.pkcs11.SunPKCS11 \
        -providerArg /foo/bar/token.config \
        -list

4.2 Policyツール

J2SE 5.0より前では、デフォルトのPolicyの実装に含まれるkeystoreエントリは次の構文を使用していました。

keystore "some_keystore_url", "keystore_type";
この構文は、PKCS#11キーストアにアクセスするには不十分でした。そのようなアクセスでは通常はPINが必要となり、またPKCS#11プロバイダ・インスタンスが複数ある場合があったためです。これらの要件を満たすために、keystoreエントリの構文はJ2SE 5.0で次のように更新されました。
keystore "some_keystore_url", "keystore_type", "keystore_provider";
keystorePasswordURL "some_password_url";
ここで、keystore_providerはキーストア・プロバイダ名(「SunPKCS11-SmartCard」など)、some_password_urlは、トークンPINの場所を指すURLです。keystore_providerkeystorePasswordURLの行はどちらもオプションです。keystore_providerが指定されていない場合、特定のキーストア・タイプをサポートする最初の構成済みプロバイダが使用されます。keystorePasswordURLの行が指定されていない場合、パスワードは使用されません。

PKCS#11トークンのキーストアPolicyエントリの例を次に示します。

keystore "NONE", "PKCS11", "SunPKCS11-SmartCard";
keystorePasswordURL "file:/foo/bar/passwordFile";

5.0 プロバイダの開発

J2SE 5.0では、java.security.Providerクラスに新しい機能が導入され、プロバイダ実装でPKCS#11トークンや暗号化サービス全般を簡単にサポートできるようになりました。これらの新機能を次に説明します。

新しい機能を例示した簡単なプロバイダについては、付録Cを参照してください。

5.1 プロバイダ・サービス

前述のプロバイダのマニュアルで説明しているとおり、J2SE 5.0より前では、サポートされているサービスを記述するjava.util.Propertyエントリを作成するためにプロバイダが必要でした。プロバイダによるサービス実装ごとに、サービスの型(CipherSignatureなど)、ピリオド、およびサービスが適用されるアルゴリズム名で構成される名前のプロパティが必要です。プロパティの値には、サービスを実装するクラスの完全修飾名を指定する必要があります。値com.sun.crypto.provider.DHKeyAgreementを持つようにKeyAgreement.DiffieHellmanプロパティを設定するプロバイダの例を次に示します。

put("KeyAgreement.DiffieHellman", 
      "com.sun.crypto.provider.DHKeyAgreement")

J2SE 5.0では、新しいpublic staticのネストされたクラスProvider.Serviceが導入され、プロバイダ・サービスのプロパティ(型、属性、アルゴリズム名、アルゴリズムの別名など)をカプセル化しやすくなります。プロバイダはProvider.putService()メソッドを呼び出すことで、Provider.Serviceオブジェクトをインスタンス化して登録できます。これは、J2SE 5.0より前で行われていた、Propertyエントリを作成してProvider.put()メソッドを呼び出すことと同じです。Provider.put経由で登録されたレガシーのPropertyエントリも引き続きサポートされています。

クラスcom.sun.crypto.provider.DHKeyAgreementによって実装され、DiffieHellmanアルゴリズムを使用する、KeyAgreement型のServiceオブジェクトを作成するプロバイダの例を次に示します。

Service s = new Service(this, "KeyAgreement", "DiffieHellman",
    "com.sun.crypto.provider.DHKeyAgreement", null, null);
putService(s);

旧バージョンのPropertyエントリのかわりにProvider.Serviceオブジェクトを使用すると、大きな利点が2つあります。1つ目として、エンジン・クラスをインスタンス化するときに、プロバイダの柔軟性が向上します。2つ目として、プロバイダでパラメータの妥当性をテストすることができます。次に、これらの特徴について説明します。

5.1.1 エンジン・クラスのインスタンス化

J2SE 5.0より前では、Java暗号化フレームワークが特定のサービスのプロバイダ・プロパティを検索し、そのプロパティで登録されたエンジン・クラスを直接インスタンス化していました。J2SE 5.0では、デフォルトで同じ動作をしますが、プロバイダはこの動作をオーバーライドし、要求されたサービス自身のエンジン・クラスをインスタンス化できます。

デフォルトの動作をオーバーライドするときは、プロバイダはカスタムな動作を追加するようにProvider.Service.newInstance()メソッドをオーバーライドします。たとえばプロバイダはカスタムのコンストラクタを呼び出したり、プロバイダの外部からはアクセスできない(プロバイダのみが知る)情報を使用して初期化を実行したりすることができます。

5.1.2 パラメータのサポート

Java暗号化フレームワークでは、プロバイダのサービス実装がアプリケーション固有のパラメータを使用できるかどうかを判断するために、すばやいチェックを試みます。このチェックを実行するために、フレームワークではProvider.Service.supportsParameter()を呼び出します。

J2SE 5.0では、フレームワークはプロバイダの遅延選択中にこのすばやいチェックを利用します。アプリケーションが初期化メソッドを呼び出してKeyオブジェクトを渡すと、フレームワークではService.supportsParameter()メソッドを呼び出すことで、配下のプロバイダに対してそのオブジェクトをサポートしているかどうかを確認します。supportsParameter()falseを返すと、フレームワークはただちにそのプロバイダを対象から取り除きます。supportsParameter()trueを返すと、フレームワークはKeyオブジェクトをそのプロバイダの初期化エンジン・クラス実装に渡します。ソフトウェアKeyオブジェクトを必要とするプロバイダでは、ソフトウェア以外の鍵を渡されたときにfalseを返すように、このメソッドをオーバーライドする必要があります。同様に、抽出不可能な鍵が含まれたPKCS#11トークンのプロバイダでは、このプロバイダが作成した、つまりそれぞれのトークン上のKeyに対応するKeyオブジェクトに対してtrueだけを返すようにしなければなりません。

supportsParameter()のデフォルト実装ではtrueを返します。これにより、既存のプロバイダを変更しないで動作させることができます。しかし、この緩やかなデフォルト実装のために、フレームワークでは、初期化エンジン・クラス実装内部のKeyオブジェクトを拒否するプロバイダが例外をスローしたときに、その例外をキャッチできるようにしておく必要があります。フレームワークでは、これらのケースをsupportsParameter()falseを返すときと同じように扱います。

付録A: Sun PKCS#11プロバイダでサポートされるアルゴリズム

次の表では、Sun PKCS#11プロバイダでサポートされるJavaアルゴリズムと、サポートに必要となる対応するPKCS#11メカニズムについてリストします。複数のメカニズムがリストされている場合は、設定されている順番に記載しており、いずれか1つを指定すれば十分です。disabledMechanismsおよびenabledMechanisms 構成指示を使用すると、メカニズムを無視するようにSunPKCS11に指示できます。

Elliptic Curveメカニズムでは、SunPKCS11は、パラメータのエンコーディングとしてnamedCurveの選択を使用する鍵のみを使用し、圧縮されていない形式のみが許可されます。Sun PKCS#11プロバイダでは、標準的な名前が付けられたすべてのドメイン・パラメータをトークンがサポートしていることが前提となります。

Javaアルゴリズム PKCS#11メカニズム
Signature.MD2withRSA CKM_MD2_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509
Signature.MD5withRSA CKM_MD5_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509
Signature.SHA1withRSA CKM_SHA1_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509
Signature.SHA224withRSA CKM_SHA224_RSA_PKCS, CKM_RSA_PKCS, CKM_RSA_X_509
Signature.SHA256withRSA CKM_SHA256_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509
Signature.SHA384withRSA CKM_SHA384_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509
Signature.SHA512withRSA CKM_SHA512_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509
Signature.SHA1withDSA CKM_DSA_SHA1、CKM_DSA
Signature.NONEwithDSA CKM_DSA
Signature.SHA1withECDSA CKM_ECDSA_SHA1、CKM_ECDSA
Signature.SHA224withECDSA CKM_ECDSA
Signature.SHA256withECDSA CKM_ECDSA
Signature.SHA384withECDSA CKM_ECDSA
Signature.SHA512withECDSA CKM_ECDSA
Signature.NONEwithECDSA CKM_ECDSA
Cipher.RSA/ECB/PKCS1Padding CKM_RSA_PKCS
Cipher.ARCFOUR CKM_RC4
Cipher.DES/CBC/NoPadding CKM_DES_CBC
Cipher.DESede/CBC/NoPadding CKM_DES3_CBC
Cipher.AES/CBC/NoPadding CKM_AES_CBC
Cipher.Blowfish/CBC/NoPadding CKM_BLOWFISH_CBC
Cipher.RSA/ECB/NoPadding CKM_RSA_X_509
Cipher.AES/CTR/NoPadding CKM_AES_CTR
KeyAgreement.ECDH CKM_ECDH1_DERIVE
KeyAgreement.DiffieHellman CKM_DH_PKCS_DERIVE
KeyPairGenerator.RSA CKM_RSA_PKCS_KEY_PAIR_GEN
KeyPairGenerator.DSA CKM_DSA_KEY_PAIR_GEN
KeyPairGenerator.EC CKM_EC_KEY_PAIR_GEN
KeyPairGenerator.DiffieHellman CKM_DH_PKCS_KEY_PAIR_GEN
KeyGenerator.ARCFOUR CKM_RC4_KEY_GEN
KeyGenerator.DES CKM_DES_KEY_GEN
KeyGenerator.DESede CKM_DES3_KEY_GEN
KeyGenerator.AES CKM_AES_KEY_GEN
KeyGenerator.Blowfish CKM_BLOWFISH_KEY_GEN
Mac.HmacMD5 CKM_MD5_HMAC
Mac.HmacSHA1 CKM_SHA_1_HMAC
Mac.HmacSHA224 CKM_SHA224_HMAC
Mac.HmacSHA256 CKM_SHA256_HMAC
Mac.HmacSHA384 CKM_SHA384_HMAC
Mac.HmacSHA512 CKM_SHA512_HMAC
MessageDigest.MD2 CKM_MD2
MessageDigest.MD5 CKM_MD5
MessageDigest.SHA1 CKM_SHA_1
MessageDigest.SHA-224 CKM_SHA224
MessageDigest.SHA-256 CKM_SHA256
MessageDigest.SHA-384 CKM_SHA384
MessageDigest.SHA-512 CKM_SHA512
KeyFactory.RSA サポートされるRSAメカニズムすべて
KeyFactory.DSA サポートされるDSAメカニズムすべて
KeyFactory.EC サポートされるECメカニズムすべて
KeyFactory.DiffieHellman サポートされるDiffie-Hellmanメカニズムすべて
SecretKeyFactory.ARCFOUR CKM_RC4
SecretKeyFactory.DES CKM_DES_CBC
SecretKeyFactory.DESede CKM_DES3_CBC
SecretKeyFactory.AES CKM_AES_CBC
SecretKeyFactory.Blowfish CKM_BLOWFISH_CBC
SecureRandom.PKCS11 CK_TOKEN_INFOにはCKF_RNGビット・セットがある
KeyStore.PKCS11 常に使用可能

付録B: Sun PKCS#11プロバイダのKeyStore要件

ここでは、Sun PKCS#11プロバイダのKeyStoreを、ベースとなるネイティブのPKCS#11ライブラリへ実装する場合の要件を説明します。より多くの既存のPKCS#11ライブラリとの相互運用性を実現するため、将来のリリースで変更が加えられる可能性があります。

読取り専用アクセス

PKCS#11トークンに保存された既存のオブジェクトをKeyStoreエントリにマッピングするため、Sun PKCS#11プロバイダのKeyStore実装は次の操作を実行します。

  1. C_FindObjects[Init|Final]を呼び出して、トークン上のすべての非公開鍵オブジェクトを検索します。検索テンプレートには、次の属性があります。
    • CKA_TOKEN = true
    • CKA_CLASS = CKO_PRIVATE_KEY
  2. C_FindObjects[Init|Final]を呼び出して、トークン上のすべての証明書オブジェクトを検索します。検索テンプレートには、次の属性があります。
    • CKA_TOKEN = true
    • CKA_CLASS = CKO_CERTIFICATE
  3. それぞれのCKA_ID属性を取得することで、各非公開鍵オブジェクトを対応する証明書と一致させます。一致するペアは、同じ一意のCKA_IDを共有する必要があります。

    一致するペアごとに、発行者 ->サブジェクトのパスに従って証明書チェーンが作成されます。エンド・エンティティ証明書から、次の属性を持つ検索テンプレートを使用したC_FindObjects[Init|Final]が呼び出されます。

    • CKA_TOKEN = true
    • CKA_CLASS = CKO_CERTIFICATE
    • CKA_SUBJECT = [DN of certificate issuer]

    この検索は、発行者の証明書が見つからないか、自己署名証明書が見つかるまで継続します。複数の証明書が見つかった場合は、最初の証明書が使用されます。

    非公開鍵と証明書が一致して証明書チェーンが作成されると、エンド・エンティティ証明書のCKA_LABELの値をKeyStoreの別名として、非公開鍵エントリに情報が保存されます。

    エンド・エンティティ証明書にCKA_LABELがない場合は、別名はCKA_IDから作成されます。CKA_IDが出力可能文字からのみ構成される場合は、CKA_IDのバイトをUTF-8文字セットを使用してデコードして、Stringの別名を作成します。または、16進数のString別名を、CKA_IDバイトから作成します(例: 「0xFFFF」)。

    複数の証明書が同一のCKA_LABELを共有する場合、別名はCKA_LABELに加え、エンド・エンティティ証明書の発行者およびシリアル番号(例: 「MyCert/CN=foobar/1234」)から作成されます。

  4. (エンド・エンティティ証明書として)非公開鍵エントリの一部ではない各証明書は、信頼できるかどうかチェックされます。CKA_TRUSTED属性がtrueの場合、CKA_LABEL値をKeyStoreの別名とする、KeyStoreの信頼できる証明書エントリが作成されます。証明書にCKA_LABELがない場合、または複数の証明書が同じCKA_LABELを共有する場合、別名は上述のように作成されます。

    CKA_TRUSTED属性がサポートされていない場合は、信頼できる証明書エントリは作成されません。

  5. 非公開鍵エントリまたは信頼できる証明書エントリの一部ではない非公開鍵または証明書オブジェクトは、すべて無視されます。
  6. C_FindObjects[Init|Final]を呼び出して、トークン上のすべての秘密鍵オブジェクトを検索します。検索テンプレートには、次の属性があります。
    • CKA_TOKEN = true
    • CKA_CLASS = CKO_SECRET_KEY

    各秘密鍵オブジェクトに対して、CKA_LABEL値をKeyStoreの別名とするKeyStore秘密鍵エントリが作成されます。各秘密鍵オブジェクトには、固有のCKA_LABELが必要です。

書込みアクセス

PKCS#11トークンにKeyStoreエントリに対する新しいKeyStoreエントリを作成するため、Sun PKCS#11プロバイダのKeyStore実装は次の操作を実行します。

  1. KeyStore.setEntryなどを使用してKeyStoreエントリを作成するには、CKA_TOKEN=trueとしてC_CreateObjectを呼び出し、エントリの内容それぞれに対してトークン・オブジェクトを作成します。

    非公開鍵オブジェクトは、CKA_PRIVATE=trueで保存されます。KeyStoreの別名(UTF-8エンコード)は、非公開鍵と対応するエンド・エンティティ証明書の両方でCKA_IDとして設定されます。KeyStoreの別名では、エンド・エンティティ証明書オブジェクトにCKA_LABELが設定されます。

    非公開鍵エントリのチェーン内の各証明書も保存されます。CKA_LABELは、CA証明書には設定されません。CA証明書がトークン内にある場合、複製は保存されません。

    秘密鍵オブジェクトは、CKA_PRIVATE=trueで保存されます。KeyStoreの別名は、CKA_LABELとして設定されます。

  2. セッション・オブジェクトをトークン・オブジェクトに変換しようとする場合(KeyStore.setEntryが呼び出され、指定されたエントリの非公開鍵オブジェクトがセッション・オブジェクトである場合など)、C_CopyObjectがCKA_TOKEN=trueとして呼び出されます。
  3. トークン内に複数の証明書が見つかり、同じCKA_LABELを共有する場合は、トークンへの書込み機能は無効になります。
  4. PKCS#11仕様では一般のアプリケーションでCKA_TRUSTED=trueと設定することが許可されないため(トークン初期化アプリケーションのみが可能)、信頼できる証明書エントリを作成できません。

その他

上記の検索のほか、Sun PKCS#11プロバイダのKeyStore実装で次の検索を使用して内部関数を実行できます。特に、次のどの属性テンプレートを使用した場合でも、C_FindObjects[Init|Final]を呼び出すことができます。

付録C: プロバイダの例

package com.foo;

import java.io.*;
import java.lang.reflect.*;
import java.security.*;
import javax.crypto.*;

/**
 * Example provider that demonstrates some of the new API features.
 *
 *  . implement multiple different algorithms in a single class.
 *    Previously each algorithm needed to be implemented in a separate class
 *    (e.g. one for SHA-256, one for SHA-384, etc.)
 *
 *  . multiple concurrent instances of the provider frontend class each
 *    associated with a different backend.
 *
 *  . it uses "unextractable" keys and lets the framework know which key
 *    objects it can and cannot support
 *
 * Note that this is only a simple example provider designed to demonstrate
 * several of the new features.  It is not explicitly designed for efficiency.
 */
public final class ExampleProvider extends Provider {

    // reference to the crypto backend that implements all the algorithms
    final CryptoBackend cryptoBackend;

    public ExampleProvider(String name, CryptoBackend cryptoBackend) {
        super(name, 1.0, "JCA/JCE provider for " + name);
        this.cryptoBackend = cryptoBackend;
        // register the algorithms we support (SHA-256, SHA-384, DESede, and AES)
        putService(new MyService
            (this, "MessageDigest", "SHA-256", "com.foo.ExampleProvider$MyMessageDigest"));
        putService(new MyService
            (this, "MessageDigest", "SHA-384", "com.foo.ExampleProvider$MyMessageDigest"));
        putService(new MyCipherService
            (this, "Cipher", "DES", "com.foo.ExampleProvider$MyCipher"));
        putService(new MyCipherService
            (this, "Cipher", "AES", "com.foo.ExampleProvider$MyCipher"));
    }

    // the API of our fictitious crypto backend
    static abstract class CryptoBackend {
        abstract byte[] digest(String algorithm, byte[] data);
        abstract byte[] encrypt(String algorithm, KeyHandle key, byte[] data);
        abstract byte[] decrypt(String algorithm, KeyHandle key, byte[] data);
        abstract KeyHandle createKey(String algorithm, byte[] keyData);
    }

    // the shell of the representation the crypto backend uses for keys
    private static final class KeyHandle {
        // fill in code
    }

    // we have our own ServiceDescription implementation that overrides newInstance()
    // that calls the (Provider, String) constructor instead of the no-args constructor
    private static class MyService extends Service {

        private static final Class[] paramTypes = {Provider.class, String.class};

        MyService(Provider provider, String type, String algorithm,
                String className) {
            super(provider, type, algorithm, className, null, null);
        }

        public Object newInstance(Object param) throws NoSuchAlgorithmException {
            try {
                // get the Class object for the implementation class
                Class clazz;
                Provider provider = getProvider();
                ClassLoader loader = provider.getClass().getClassLoader();
                if (loader == null) {
                    clazz = Class.forName(getClassName());
                } else {
                    clazz = loader.loadClass(getClassName());
                }
                // fetch the (Provider, String) constructor
                Constructor cons = clazz.getConstructor(paramTypes);
                // invoke constructor and return the SPI object
                Object obj = cons.newInstance(new Object[] {provider, getAlgorithm()});
                return obj;
            } catch (Exception e) {
                throw new NoSuchAlgorithmException("Could not instantiate service", e);
            }
        }
    }

    // custom ServiceDescription class for Cipher objects. See supportsParameter() below
    private static class MyCipherService extends MyService {
        MyCipherService(Provider provider, String type, String algorithm,
                String className) {
            super(provider, type, algorithm, className);
        }
        // we override supportsParameter() to let the framework know which
        // keys we can support. We support instances of MySecretKey, if they
        // are stored in our provider backend, plus SecretKeys with a RAW encoding.
        public boolean supportsParameter(Object obj) {
            if (obj instanceof SecretKey == false) {
                return false;
            }
            SecretKey key = (SecretKey)obj;
            if (key.getAlgorithm().equals(getAlgorithm()) == false) {
                return false;
            }
            if (key instanceof MySecretKey) {
                MySecretKey myKey = (MySecretKey)key;
                return myKey.provider == getProvider();
            } else {
                return "RAW".equals(key.getFormat());
            }
        }
    }

    // our generic MessageDigest implementation. It implements all digest
    // algorithms in a single class. We only implement the bare minimum
    // of MessageDigestSpi methods
    private static final class MyMessageDigest extends MessageDigestSpi {
        private final ExampleProvider provider;
        private final String algorithm;
        private ByteArrayOutputStream buffer;
        MyMessageDigest(Provider provider, String algorithm) {
            super();
            this.provider = (ExampleProvider)provider;
            this.algorithm = algorithm;
            engineReset();
        }
        protected void engineReset() {
            buffer = new ByteArrayOutputStream();
        }
        protected void engineUpdate(byte b) {
            buffer.write(b);
        }
        protected void engineUpdate(byte[] b, int ofs, int len) {
            buffer.write(b, ofs, len);
        }
        protected byte[] engineDigest() {
            byte[] data = buffer.toByteArray();
            byte[] digest = provider.cryptoBackend.digest(algorithm, data);
            engineReset();
            return digest;
        }
    }

    // our generic Cipher implementation, only partially complete. It implements
    // all cipher algorithms in a single class. We implement only as many of the
    // CipherSpi methods as required to show how it could work
    private static abstract class MyCipher extends CipherSpi {
        private final ExampleProvider provider;
        private final String algorithm;
        private int opmode;
        private MySecretKey myKey;
        private ByteArrayOutputStream buffer;
        MyCipher(Provider provider, String algorithm) {
            super();
            this.provider = (ExampleProvider)provider;
            this.algorithm = algorithm;
        }
        protected void engineInit(int opmode, Key key, SecureRandom random)
                throws InvalidKeyException {
            this.opmode = opmode;
            myKey = MySecretKey.getKey(provider, algorithm, key);
            if (myKey == null) {
                throw new InvalidKeyException();
            }
            buffer = new ByteArrayOutputStream();
        }
        protected byte[] engineUpdate(byte[] b, int ofs, int len) {
            buffer.write(b, ofs, len);
            return new byte[0];
        }
        protected int engineUpdate(byte[] b, int ofs, int len, byte[] out, int outOfs) {
            buffer.write(b, ofs, len);
            return 0;
        }
        protected byte[] engineDoFinal(byte[] b, int ofs, int len) {
            buffer.write(b, ofs, len);
            byte[] in = buffer.toByteArray();
            byte[] out;
            if (opmode == Cipher.ENCRYPT_MODE) {
                out = provider.cryptoBackend.encrypt(algorithm, myKey.handle, in);
            } else {
                out = provider.cryptoBackend.decrypt(algorithm, myKey.handle, in);
            }
            buffer = new ByteArrayOutputStream();
            return out;
        }
        // code for remaining CipherSpi methods goes here
    }

    // our SecretKey implementation. All our keys are stored in our crypto
    // backend, we only have an opaque handle available. There is no
    // encoded form of these keys.
    private static final class MySecretKey implements SecretKey {

        final String algorithm;
        final Provider provider;
        final KeyHandle handle;

        MySecretKey(Provider provider, String algorithm, KeyHandle handle) {
            super();
            this.provider = provider;
            this.algorithm = algorithm;
            this.handle = handle;
        }
        public String getAlgorithm() {
            return algorithm;
        }
        public String getFormat() {
            return null; // this key has no encoded form
        }
        public byte[] getEncoded() {
            return null; // this key has no encoded form
        }
        // Convert the given key to a key of the specified provider, if possible
        static MySecretKey getKey(ExampleProvider provider, String algorithm, Key key) {
            if (key instanceof SecretKey == false) {
                return null;
            }
            // algorithm name must match
            if (!key.getAlgorithm().equals(algorithm)) {
                return null;
            }
            // if key is already an instance of MySecretKey and is stored
            // on this provider, return it right away
            if (key instanceof MySecretKey) {
                MySecretKey myKey = (MySecretKey)key;
                if (myKey.provider == provider) {
                    return myKey;
                }
            }
            // otherwise, if the input key has a RAW encoding, convert it
            if (!"RAW".equals(key.getFormat())) {
                return null;
            }
            byte[] encoded = key.getEncoded();
            KeyHandle handle = provider.cryptoBackend.createKey(algorithm, encoded);
            return new MySecretKey(provider, algorithm, handle);
        }
    }
}

Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.