1.0 はじめに
2.0 Sun PKCS#11プロバイダ
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プロバイダは、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の表に示します。
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 = 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メカニズムのリスト。空白文字で区切り、全体を中カッコ()で囲む | これは、Sun PKCS#11プロバイダと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を使用して持続的な鍵を作成することはできません。 |
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
PKCS#11に問題が発生し、デバッグが必要になることがあります。ライブラリ、スロット、トークンおよびメカニズムのデバッグ情報を表示するには、$JAVA-HOME/jre/lib/security/sunpkcs11-solaris.cfg
ファイルにshowInfo=true
を追加します。
その他のデバッグ情報については、次のいずれかのオプションを指定してJavaプロセスを起動または再起動してください。
-Djava.security.debug=sunpkcs11
-Djava.security.debug=pkcs11keystore
トラブルシューティング・プロセスの一環として、PKCS#11プロバイダ、または特定のプロバイダに固有のメカニズムを一時的に無効にすると便利な場合があります。これはあくまでも一時的な措置であることに注意してください。PKCS#11プロバイダを無効にすると、そのプロバイダは使用できなくなるため、アプリケーションが破損したり、アプリケーションのパフォーマンスに影響が出たりする可能性があります。問題が特定されたら、その固有のメカニズムのみを無効のままにしてください。
PKCS#11プロバイダは、次のいずれかの方法で無効にできます。
-Dsun.security.pkcs11.enable-solaris=false
sun.security.pkcs11.SunPKCS11、/lib/security/sunpkcs11-solaris.cfg,、/usr/lib/libpkcs11.so
)に基づくSunPKCS11プロバイダにのみ適用されます。
$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プロセスを起動または再起動します。
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
ファイルの前述の部分は、無効にするメカニズムを追加する場所を示す例であり、完全なファイルではありません。
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("AES")などの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.Serviceオブジェクトを使用すると、大きな利点が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.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 | 常に使用可能 |
ここでは、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 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); } } }