1.0 はじめに
3.0 アプリケーション開発者
4.0 ツール
5.0 プロバイダ開発者
付録 A Sun PKCS#11 プロバイダでサポートされるアルゴリズム
付録 B Sun PKCS#11 プロバイダの KeyStore 要件
付録 C プロバイダの例
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 に加えられた機能の向上点についても説明します。
その他の多くのプロバイダとは異なり、Sun PKCS#11 プロバイダ自身は暗号化アルゴリズムを実装していません。その代わりに、Java JCA および JCE API とネイティブ PKCS#11 暗号化 API との間のブリッジとして動作し、これらの間の呼び出しと規則を変換します。つまり、標準 JCA および JCE API を呼び出す Java アプリケーションなら、アプリケーションを変更しなくても、基盤となる次のような PKCS#11 実装で提供されるアルゴリズムを利用できるということです。
Sun PKCS#11 プロバイダでは、PKCS#11 v2.0 以降の実装がシステムにインストールされていなければなりません。この実装は、共有オブジェクトライブラリ (Solaris および Linux での .so) またはダイナミックリンクライブラリ (Windows での .dll) の形態である必要があります。使用する暗号化デバイスにこのような PKCS#11 実装が含まれているかどうかを調べる方法、実装を構成する方法、およびライブラリファイルのファイル名については、ベンダーが提供するマニュアルを参照してください。
Sun PKCS#11 プロバイダでは、基盤となる PKCS#11 実装で提供されるかぎり、多くのアルゴリズムをサポートしています。アルゴリズムとそれらに対応する PKCS#11 メカニズムを、付録 A の表に示します。
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 ommitted 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 = valueattribute と value の有効な値については、このセクションの表で説明しています。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 を使用します。指定できるのは、多くとも slot か slotListIndex のどちらか 1 つです。どちらも指定しない場合のデフォルトは、値 0 の slotListIndex です。 |
slotListIndex | スロットのインデックス | このプロバイダインスタンスが関連付けされているスロットのインデックス。これは、PKCS#11 の関数 C_GetSlotList で返されるすべてのスロットのリストに対するインデックスです。たとえば、0 はリストの先頭のスロットを表します。指定できるのは、多くとも slot か slotListIndex のどちらか 1 つです。どちらも指定しない場合のデフォルトは、値 0 の slotListIndex です。 |
enabledMechanisms | 有効にする PKCS#11 メカニズムのリスト。空白文字で区切り、全体を中括弧 ( ) で囲む | このプロバイダインスタンスが使用する PKCS#11 メカニズムのリスト。これらのメカニズムは、Sun PKCS#11 プロバイダと PKCS#11 トークンの両方でサポートされている必要があります。その他のメカニズムはすべて無視されます。リスト内の各エントリは、PKCS#11 メカニズムの名前になります。2 つの PKCS#11 メカニズムから成るリストの例を次に示します。
enabledMechanisms = { CKM_RSA_PKCS CKM_RSA_PKCS_KEY_PAIR_GEN }指定できるのは、多くとも enabledMechanisms か disabledMechanisms のどちらか 1 つです。どちらも指定しない場合、有効なメカニズムは、Sun PKCS#11 プロバイダと PKCS#11 トークンの両方でサポートされているメカニズムになります。 |
disabledMechanisms | 無効にする PKCS#11 メカニズムのリスト。空白文字で区切り、全体を中括弧 ( ) で囲む | このプロバイダインスタンスが無視する PKCS#11 メカニズムのリスト。リストされたあらゆるメカニズムは、トークンと Sun PKCS#11 プロバイダでサポートされていても、プロバイダによって無視されます。これらのサービスを無効にするために文字列 SecureRandom および KeyStore を指定できます。
指定できるのは、多くとも enabledMechanisms か disabledMechanisms のどちらか 1 つです。どちらも指定しない場合、有効なメカニズムは、Sun PKCS#11 プロバイダと PKCS#11 トークンの両方でサポートされているメカニズムになります。 |
attributes | 下記参照 | attributes オプションは、PKCS#11 鍵オブジェクトの作成時に設定される追加の PKCS#11 属性を指定するために使用します。これにより、特定の属性を必要とするトークンを使用できるようになります。詳細は、次のセクションを参照してください。 |
attributes オプションは、PKCS#11 実装で割り当てたデフォルト値を使用しない場合や、PKCS#11 実装でデフォルト値をサポートしないために、明示的に値を指定する必要がある場合に使用します。使用している PKCS#11 実装でサポートしない属性や、該当する鍵タイプで無効な属性を指定すると、実行時に操作が失敗する原因となります。
オプションは 0 回または複数回指定できます。また、次に説明するように、構成ファイルで指定した順番でオプションが処理されます。attributes オプションは次の書式です。
attributes(operation, keytype, keyalgorithm) = { name1 = value1 [...] }operation の有効な値は次のとおりです。
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(*,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 = true と CKA_DECRYPT = true 両方のセットがあることになります。
attributes オプションには特殊な形式もあります。構成ファイルに attributes = compatibility と書くことができます。これは、属性ステートメントの全体的なセットに対するショートカットです。これはプロバイダが既存の Java アプリケーションとの互換性を最大限に保つことを目的としています。これにより、Java アプリケーションでは、たとえばすべての鍵コンポーネントにアクセス可能で、秘密鍵を暗号化および復号化の両方に使用可能であることが期待されます。compatibility 属性の行は、ほかの attributes の行とともに使用できます。この場合は先に説明したような、同様のアグリゲーションおよびオーバーライドの規則が適用されます。
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 固有のコードを使用します。この場合、通常の構成コマンド library
、slot
、および slotListIndex
は使用できません。
属性 | 値 | 説明 |
---|---|---|
nssLibraryDirectory | NSS および NSPR ライブラリを含むディレクトリ | NSS および NSPR ライブラリを含むディレクトリのフルパス名。Java VM として同じプロセス内で実行されている別のコンポーネントによって NSS がすでにロードおよび初期化されていないかぎり、この属性を指定する必要があります。
プラットフォームに応じて、このディレクトリを含めるために |
nssSecmodDirectory | NSS DB ファイルを含むディレクトリ | NSS 構成および鍵情報 (secmod.db 、key3.db 、および cert8.db ) を含むディレクトリのフルパス名。別のコンポーネントによって NSS がすでに初期化されていないか (上記参照)、または次に説明されているようにデータベースファイルなしで NSS が使用されないかぎり、この指示を指定する必要があります。 |
nssDbMode | readWrite 、readOnly 、noDb のいずれか |
この指示は、NSS データベースへのアクセス方法を決定します。読み書きモードではフルアクセスが可能ですが、データベースには一度に 1 つのプロセスのみがアクセスする必要があります。読み取り専用モードでは、このファイルの変更は許可されません。
noDb モードを使用すると、データベースファイルなしで純粋に暗号化プロバイダとして NSS を使用できます。PKCS11 KeyStore を使用して持続的な鍵を作成することはできません。NSS には、バンドルされている Sun の Java ベース暗号化プロバイダ (Elliptic Curve Cryptography (ECC) など) では現在使用できない高度に最適化された実装およびアルゴリズムが含まれているため、このモードは有用です。 |
nssModule | keystore 、crypto 、fips 、trustanchors のいずれか |
さまざまなライブラリやスロットを使用して NSS の機能が使用できるようになっています。この指示は、SunPKCS11 のインスタンスがアクセスするモジュールを決定します。
FIPS-140 準拠モードに対して NSS の
信頼できるアンカーライブラリを含むように |
name = NSScrypto nssLibraryDirectory = /opt/tests/nss/lib nssDbMode = noDb attributes = compatibility
name = NSSfips nssLibraryDirectory = /opt/tests/nss/lib nssSecmodDirectory = /opt/tests/nss/fipsdb nssModule = fips
Java アプリケーションは、既存の JCA および JCE API を使用して、Sun PKCS#11 プロバイダ経由で PKCS#11 トークンにアクセスできます。多くのアプリケーションではこれで十分ですが、抽出不可能な鍵や動的に変更されるスマートカードといった一部の PKCS#11 機能を扱うには難しい場合があります。そのため、一部の PKCS#11 機能を使用するアプリケーションのサポートを向上できるように、多くの拡張機能が API に加えられました。このセクションでは、そのような拡張機能について説明します。
非公開鍵へのアクセスなど一部の 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());
Java の Key
オブジェクトには、実際の鍵データが含まれる場合も含まれない場合もあります。
アプリケーションおよびプロバイダでは、各種の Key オブジェクトを表すのに適切なインタフェースを使用する必要があります。ソフトウェア Key オブジェクト (または実際の鍵データにアクセスできる任意の Key オブジェクト) では、java.security.interfaces および javax.crypto.interfaces パッケージにインタフェース (DSAPrivateKey など) を実装する必要があります。抽出不可能なトークン鍵を表す Key オブジェクトでは、java.security および javax.crypto パッケージに、関連するジェネリックインタフェース (PrivateKey、PublicKey、または SecretKey) だけを実装する必要があります。鍵アルゴリズムの特定は、Key.getAlgorithm() メソッドを使用して実行されます。
アプリケーションでは、抽出不可能なトークン鍵の Key オブジェクトは、そのトークンに関連付けされたプロバイダだけが使用できることに注意しなければなりません。
J2SE 5.0 より前では、Cipher.getInstance("DES") などの Java 暗号化 getInstance() メソッドは、要求されたアルゴリズムを実装している最初のプロバイダからの実装を返していました。これは、ソフトウェア Key オブジェクトだけを使用できるプロバイダで、抽出不可能なトークン鍵の Key オブジェクトをアプリケーションが使用しようとする場合に問題がありました。このような場合、プロバイダは InvalidKeyException をスローします。これは、Cipher、KeyAgreement、Mac、Signature の各クラスの問題です。
J2SE 5.0 では、関連する初期化メソッドが呼び出されるまでプロバイダの選択を遅らせることで、この問題に対応しています。初期化メソッドでは Key オブジェクトを受け入れ、指定した Key オブジェクトを受け入れられるプロバイダをその時点で判断できます。これにより、選択したプロバイダで、指定した Key オブジェクトを使用できることが保証されます。次に、影響のある初期化メソッドを示します。
このプロバイダの遅延選択は、アプリケーションからは認識されませんが、Cipher、KeyAgreement、Mac、および Signature の getProvider()
メソッドの動作に影響します。getProvider()
が初期化操作の発生する前 (したがって、プロバイダ選択が起こる前) に呼び出された場合は、要求されるアルゴリズムがサポートされている最初のプロバイダが返されます。このプロバイダは、初期化メソッドが呼び出されたあとに選択されたプロバイダと同じにならない場合があります。getProvider()
が初期化操作のあとに呼び出された場合は、実際に選択されたプロバイダが返されます。アプリケーションでは関連する初期化メソッドを呼び出したあとにのみ、getProvider()
を呼び出すようにしてください。
getProvider()
だけでなく、次のメソッドにも同様の影響があります。
次のオプションを使用して KeyStoreLoginModule を構成すると、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; };
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」)。
J2SE 5.0 では、セキュリティーツールが更新され、新しい Sun PKCS#11 プロバイダを使用した操作がサポートされました。変更内容を次に説明します。
Sun PKCS#11 プロバイダが Java ランタイムの $JAVA_HOME/lib/security ディレクトリ内にある java.security セキュリティープロパティーファイルで構成されている場合、次のオプションを使用することで、PKCS#11 トークンの操作に keytool および jarsigner を使用できます。
keytool -keystore NONE -storetype PKCS11 -listPIN は -storepass オプションで指定できます。指定されない場合、keytool および jarsigner では、トークン PIN を要求します。トークンに保護された認証パス (専用の PIN パッドや生体読み取り機など) がある場合、-protected オプションを指定する必要がありますが、パスワードオプションを指定する必要はありません。
複数の Sun PKCS#11 プロバイダを java.security セキュリティープロパティーファイル内で構成した場合は、-providerName オプションを使用して、特定のプロバイダインスタンスを対象にします。このオプションの引数は、プロバイダの名前です。
keytool -keystore NONE -storetype PKCS11 \ -providerName SunPKCS11-SmartCard \ -list
Sun PKCS#11 プロバイダを java.security セキュリティープロパティーファイル内で構成していない場合は、次のオプションを使用して、プロバイダを動的にインストールするように keytool および jarsigner を設定します。
keytool -keystore NONE -storetype PKCS11 \ -providerClass sun.security.pkcs11.SunPKCS11 \ -providerArg /foo/bar/token.config \ -list
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_provider と keystorePasswordURL の行はどちらもオプションです。keystore_provider が指定されていない場合、特定のキーストアタイプをサポートする最初の構成済みプロバイダが使用されます。keystorePasswordURL の行が指定されていない場合、パスワードは使用されません。
PKCS#11 トークンのキーストア Policy エントリの例を次に示します。
keystore "NONE", "PKCS11", "SunPKCS11-SmartCard"; keystorePasswordURL "file:/foo/bar/passwordFile";
J2SE 5.0 では、java.security.Provider クラスに新しい機能が導入され、プロバイダ実装で PKCS#11 トークンや暗号化サービス全般を簡単にサポートできるようになりました。これらの新機能を次に説明します。
新しい機能を例示した簡単なプロバイダについては、付録 C を参照してください。
前述のプロバイダのマニュアルで説明しているとおり、J2SE 5.0 より前では、サポートされているサービスを記述する java.util.Property エントリを作成するためにプロバイダが必要でした。プロバイダによるサービス実装ごとに、サービスの型 (Cipher、Signature など)、ピリオド、およびサービスが適用されるアルゴリズム名で構成される名前のプロパティーが必要です。プロパティーの値には、サービスを実装するクラスの完全修飾名を指定する必要があります。値 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.Servicee オブジェクトを使用すると、大きな利点が 2 つあります。1 つ目として、エンジンクラスをインスタンス化するときに、プロバイダの柔軟性が向上します。2 つ目として、プロバイダでパラメータの妥当性をテストすることができます。次に、これらの特徴について説明します。
J2SE 5.0 より前では、Java 暗号化フレームワークが特定のサービスのプロバイダプロパティーを検索し、そのプロパティーで登録されたエンジンクラスを直接インスタンス化していました。J2SE 5.0 では、デフォルトで同じ動作をしますが、プロバイダはこの動作をオーバーライドし、要求されたサービス自身のエンジンクラスをインスタンス化できます。
デフォルトの動作をオーバーライドするときは、プロバイダはカスタムな動作を追加するように Provider.Service.newInstance() メソッドをオーバーライドします。たとえばプロバイダはカスタムのコンストラクタを呼び出したり、プロバイダの外部からはアクセスできない (プロバイダのみが知る) 情報を使用して初期化を実行したりすることができます。
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
を返すときと同じように扱います。
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.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.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.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-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 | 常に使用可能 |
ここでは、Sun PKCS#11 プロバイダの KeyStore を、ベースとなるネイティブの PKCS#11 ライブラリへ実装する場合の要件を説明します。より多くの既存の PKCS#11 ライブラリとの相互運用性を実現するため、将来のリリースで変更が加えられる可能性があります。
PKCS#11 トークンに保存された既存のオブジェクトを KeyStore エントリにマッピングするため、Sun PKCS#11 プロバイダの KeyStore 実装は次の操作を実行します。
一致するペアごとに、発行者 -> サブジェクトのパスに従って証明書チェーンが作成されます。エンドエンティティー証明書から、次の属性を持つ検索テンプレートを使用した C_FindObjects[Init|Final] が呼び出されます。
この検索は、発行者の証明書が見つからないか、自己署名証明書が見つかるまで継続します。複数の証明書が見つかった場合は、最初の証明書が使用されます。
非公開鍵と証明書が一致して証明書チェーンが作成されると、エンドエンティティー証明書の 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」) から作成されます。
CKA_TRUSTED 属性がサポートされていない場合は、信頼できる証明書エントリは作成されません。
各秘密鍵オブジェクトに対して、CKA_LABEL 値を KeyStore の別名とする KeyStore 秘密鍵エントリが作成されます。各秘密鍵オブジェクトには、固有の CKA_LABEL が必要です。
PKCS#11 トークンに KeyStore エントリに対する新しい KeyStore エントリを作成するため、Sun PKCS#11 プロバイダの KeyStore 実装は次の操作を実行します。
非公開鍵オブジェクトは、CKA_PRIVATE=true で保存されます。KeyStore の別名 (UTF-8 エンコード) は、非公開鍵と対応するエンドエンティティー証明書の両方で CKA_ID として設定されます。KeyStore の別名では、エンドエンティティー証明書オブジェクトに CKA_LABEL が設定されます。
非公開鍵エントリのチェーン内の各証明書も保存されます。CKA_LABEL は、CA 証明書には設定されません。CA 証明書がトークン内にある場合、複製は保存されません。
秘密鍵オブジェクトは、CKA_PRIVATE=true で保存されます。KeyStore の別名は、CKA_LABEL として設定されます。
上記の検索のほか、Sun PKCS#11 プロバイダの KeyStore 実装で次の検索を使用して内部関数を実行できます。特に、次のどの属性テンプレートを使用した場合でも、C_FindObjects[Init|Final] を呼び出すことができます。
CKA_TOKEN true CKA_CLASS CKO_CERTIFICATE CKA_SUBJECT [subject DN]
CKA_TOKEN true CKA_CLASS CKO_SECRET_KEY CKA_LABEL [label]
CKA_TOKEN true CKA_CLASS CKO_CERTIFICATE or CKO_PRIVATE_KEY CKA_ID [cka_id]
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 MD5, one for SHA-1, 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 (MD5, SHA1, DES, and AES) putService(new MyService (this, "MessageDigest", "MD5", "com.foo.ExampleProvider$MyMessageDigest")); putService(new MyService (this, "MessageDigest", "SHA1", "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); } } }