ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
Oracle Solaris 11 セキュリティーサービス開発ガイド Oracle Solaris 11 Information Library (日本語) |
1. Oracle Solaris の開発者向けセキュリティー機能 (概要)
3. PAM アプリケーションおよび PAM サービスの記述
GSS-API におけるコンテキストのエクスポートとインポート
8. Oracle Solaris 暗号化フレームワークの紹介
9. ユーザーレベルの暗号化アプリケーションとプロバイダの記述
ここでは、GSS-API の重要な概念である、 主体、GSS-API データ型、GSS-API 状態コード、および GSS-API トークンについて説明します。
ここでは、主な GSS-API データ型について説明します。 すべての GSS-API データ型を確認するには、「GSS-API データ型と値」を参照してください。
int のサイズはプラットフォームによって異なるため、GSS-API は次の整数型を提供します。OM_uint32。これは、32 ビットの符号なし整数です。
GSS-API はすべてのデータを内部形式で処理するため、文字列も、GSS-API 形式に変換したあとで GSS-API 関数に渡す必要があります。 GSS-API は、gss_buffer_desc 構造体を使って文字列を処理します。
typedef struct gss_buffer_desc_struct { size_t length; void *value; } gss_buffer_desc *gss_buffer_t;
gss_buffer_t は、そうした構造体へのポインタです。 文字列は、それらを使用する関数に渡す前に gss_buffer_desc 構造体に変換しておく必要があります。 次の例では、汎用的な GSS-API 関数を使って、送信前のメッセージに保護を適用しています。
例 4-1 GSS-API における文字列の使用法
char *message_string; gss_buffer_desc input_msg_buffer; input_msg_buffer.value = message_string; input_msg_buffer.length = strlen(input_msg_buffer.value) + 1; gss_generic_function(arg1, &input_msg_buffer, arg2...); gss_release_buffer(input_msg_buffer);
ここで、input_msg_buffer は、終了時に gss_release_buffer() を使って解放する必要がある点に注意してください。
gss_buffer_desc オブジェクトは文字列だけに使用されるわけではありません。 たとえば、トークンも gss_buffer_desc オブジェクトとして処理されます。 詳細は、「GSS-API トークン」を参照してください。
「名前」は主体を指します。 ネットワークセキュリティーの用語では、「主体」とは、ユーザー、プログラム、またはマシンを指します。 主体はクライアントまたはサーバーのどちらにでもなり得ます。
主体の例を、次にいくつか挙げます。
別のマシンにログインするユーザー (user@machine など)
ネットワークサービス (nfs@machine など)
アプリケーションを実行するマシン (myHost@eng.company.com など)
GSS-API では、名前は gss_name_t オブジェクトとして格納されます。このオブジェクトはアプリケーションに対して不透明です。 名前を gss_buffer_t オブジェクトから gss_name_t 形式に変換するには、gss_import_name() 関数を使用します。 インポートされたすべての名前には関連する「名前型」が割り当てられます。名前型とは、その名前の形式を示すものです。 名前型については、「GSS-API の OID」を参照してください。 有効な名前型の一覧については、「名前型」を参照してください。
gss_import_name() の構文は次のとおりです。
OM_uint32 gss_import_name ( OM_uint32 *minor-status, const gss_buffer_t input-name-buffer, const gss_OID input-name-type, gss_name_t *output-name)
実際の機構から戻される状態コード。 「GSS-API 状態コード」を参照してください。
インポートされた名前が格納される gss_buffer_desc 構造体。 この構造体は、アプリケーション側で明示的に割り当てる必要があります。 「GSS-API の文字列とそれに類するデータ」および例 4-2 を参照してください。 この引数は、アプリケーションでの使用終了後、gss_release_buffer() を使って解放する必要があります。
input-name-buffer の形式を示す gss_OID。 「GSS-API における名前型」を参照してください。 また、「名前型」に、有効な名前型の一覧表があります。
名前を受け取る gss_name_t 構造体。
次に示すのは、例 4-1 の汎用例に若干の変更を加えたものであり、gss_import_name() の使用法を示しています。 まず、通常の文字列が gss_buffer_desc 構造体に挿入されています。 次に、その文字列が、gss_import_name () によって gss_name_t 構造体内に格納されています。
例 4-2 gss_import_name()() の使用例
char *name_string; gss_buffer_desc input_name_buffer; gss_name_t output_name_buffer; input_name_buffer.value = name_string; input_name_buffer.length = strlen(input_name_buffer.value) + 1; gss_import_name(&minor_status, input_name_buffer, GSS_C_NT_HOSTBASED_SERVICE, &output_name); gss_release_buffer(input_name_buffer);
インポートされた名前は、人間が読める形式で表示できるように、元の gss_buffer_t オブジェクトに戻すことが可能です。それには、gss_display_name() を使用します。 ただし、gss_display_name() は、結果の文字列が元と同じであることを保証しません。その原因は、実際の機構が名前を格納する方法にあります。 GSS-API にはほかにも名前を処理する関数があります。 「GSS-API 関数」を参照してください。
gss_name_t 構造体には、単一の名前の複数のバージョンを格納できます。 GSS-API によってサポートされている機構ごとに、1 つのバージョンが生成されます。 つまり、user@company の gss_name_t 構造体には、Kerberos v5 から提供されたその名前の 1 つのバージョンと、別の機構から提供された別のバージョンが含まれる可能性があります。 関数 gss_canonicalize_name() は、入力として内部名と機構を受け取ります。また、gss_canonicalize_name() は、出力としてその機構に固有の単一バージョン名だけを含む別の内部名を返します。
そうした機構に固有な名前のことを「機構名 (Mechanism Name、MN)」と呼びます。 機構名とは、特定の機構の名前ではなく、特定の機構によって生成された主体の名前です。 このプロセスを示したのが次の図です。
図 4-3 内部名と機構名 (MN)
サーバーがクライアントからある名前を受け取り、その名前をアクセス制御リスト内で検索する必要がある場合を考えます。 「アクセス制御リスト (Access Control List、ACL)」とは、特定のアクセス権を持つ主体のリストのことです。
そうした検索を行うには、次のような方法が考えられます。
gss_import_name() で、クライアント名を GSS-API 内部形式にインポートします (まだインポートされていない場合)。
場合によっては、サーバーは名前を内部形式で受け取ります。その場合、この手順は必要ありません。 たとえば、サーバーはクライアント自身の名前を検索する可能性があります。 コンテキストの起動中、クライアント自身の名前は内部形式で渡されます。
gss_import_name() で、各 ACL 名を インポートします。
次の図に、このプロセスを示します。 ここでは手順 1 が必要であると仮定します。
図 4-4 名前の比較 (遅い)
名前の数が少ない場合は、名前を個別に比較する上記の方法でも問題ありません。 名前の数が非常に多い場合は、gss_canonicalize_name() 関数を使用するほうが効率的です。
この方法では次の手順を使用します。
gss_import_name() で、クライアント名をインポートします (まだインポートされていない場合)。
名前を比較する前述の方法と同様に、名前がすでに内部形式である場合には、この手順は必要ありません。
gss_canonicalize_name() を使用してクライアント名の機構名バージョンを生成します。
gss_export_name() を使用してエクスポート名を生成します。エクスポート名は、連続した文字列としてのクライアント名です。
memcmp() を使用してエクスポートされたクライアント名を ACL 内の個々の名前と比較します。これは、高速で動作するオーバーヘッドの少ない関数です。
次の図に、このプロセスを示します。 ここでも、サーバーがクライアントから受信した名前をインポートする必要があると仮定します。
図 4-5 名前の比較 (速い)
gss_export_name() は機構名 (MN) を期待するため、先にクライアント名に対して gss_canonicalize_name() を実行する必要があります。
詳細は、gss_export_name(3GSS)、gss_import_name(3GSS)、および gss_canonicalize_name(3GSS) を参照してください。
オブジェクト識別子 (Object Identifier、OID) は、次のようなデータを格納するときに使用します。
OID は、GSS-API の gss_OID_desc 構造体に格納されます。 次の例のように、GSS-API はその構造体へのポインタ gss_OID を提供します。
例 4-3 OID の構造体
typedef struct gss_OID_desc_struct { OM_uint32 length; void *elements; } gss_OID_desc, *gss_OID;
さらに、1 つ以上の OID を gss_OID_set_desc 構造体に格納することもできます。
例 4-4 OID セットの構造体
typedef struct gss_OID_set_desc_struct { size_t count; gss_OID elements; } gss_OID_set_desc, *gss_OID_set;
GSS-API では、使用するセキュリティー機構をアプリケーションが選択できるようになっていますが、GSS-API が選択したデフォルトの機構をできる限り使用する必要があります。 同様に、GSS-API では、データ保護の保護品質レベルをアプリケーションが指定できるようになっていますが、デフォルトの QOP をできる限り使用する必要があります。 デフォルトの機構を受け入れることを示すには、機構または QOP を期待する関数に値 GSS_C_NULL_OID を引数として渡します。
注意 - セキュリティー機構または QOP を明示的に指定することは、GSS-API の使用目的に反します。 そうした特定の選択は、アプリケーションの移植性を制限します。 ほかの GSS-API 実装は、その QOP または機構を意図した方法でサポートしていない可能性があります。 ただし、付録 C OID の指定では、利用可能な機構や QOP を知る方法と、それらの選択方法について、簡単に説明しています。 |
QOP とセキュリティー機構のほかに、名前型を示すためにも OID が使用されます。名前型とは、関連する名前の形式を示すものです。 たとえば、gss_import_name() 関数は主体の名前を文字列から gss_name_t 型に変換しますが、この関数は変換すべき文字列の形式を引数の 1 つとして受け取ります。 たとえば、名前型が GSS_C_NT_HOSTBASED_SERVICE である場合、この関数は、入力された名前が service@host 形式であると判断します。 名前型が GSS_C_NT_EXPORT_NAME である場合、この関数は GSS-API エクスポート名を期待します。 アプリケーションは gss_inquire_names_for_mech() 関数を使用すると、指定した機構で使用できる名前型を知ることができます。 GSS-API によって使用される名前型の一覧については、「名前型」を参照してください。
すべての GSS-API 関数は、関数の成功または失敗に関する情報を提供する 2 種類のコードを返します。 どちらの種類の状態コードも OM_uint32 値として戻されます。
次に、この 2 種類の戻りコードについて説明します。
メジャー状態コード
メジャー状態コードは次のエラーを示します。
汎用 GSS-API ルーチンエラー (ルーチンに無効な機構を指定したなど)
特定の GSS-API 言語バインディングに固有の呼び出しエラー (関数の引数が読み書きできない、引数の形式が間違っているなど)
両方のタイプのエラー
さらに、メジャー状態コードは、ルーチンの状態に関する補足情報も提供できます。 たとえば、処理が終了していない、トークンの送信順が間違っている、などを示すコードが返されます。 何もエラーが発生しなかった場合、ルーチンは値が GSS_S_COMPLETE のメジャー状態コードを戻します。
メジャー状態コードは次のようにして返されます。
OM_uint32 major_status ; /* status returned by GSS-API */ major_status = gss_generic_function(arg1, arg2 ...);
メジャー状態戻りコードは他の OM_uint32 と同じように処理できます。 たとえば、次のコードを考えます。
OM_uint32 maj_stat; maj_sta = gss_generic_function(arg1, arg2 ...); if (maj_stat == GSS_CREDENTIALS_EXPIRED) <do something...>
メジャー状態コードは、マクロ GSS_ROUTINE_ERROR()、GSS_CALLING_ERROR()、および GSS_SUPPLEMENTARY_INFO() で処理できます。 「GSS-API 状態コード」 では、メジャー状態コードの読み取り方法について説明しているほか、GSS-API 状態コードの一覧を提供しています。
マイナー状態コード
マイナー状態コードは実際の機構から戻されます。 これらのコードについては、このマニュアルでは具体的に説明しません。
すべての GSS-API 関数は最初の引数として OM_uint32 型のマイナー状態コードを受け取ります。 関数が呼び出し元の関数に制御を戻す際に、その OM_uint32 引数にマイナー状態コードが格納されます。 次のコードを考えます。
OM_uint32 *minor_status ; /* status returned by mech */ major_status = gss_generic_function(&minor_status, arg1, arg2 ...);
致命的なエラーを示すメジャー状態コードが返される場合でも、minor_status パラメータは GSS-API ルーチンによって必ず設定されます。 その他のほとんどの出力パラメータには値が設定されません。 ただし、ルーチンによって割り当てられた記憶領域へのポインタを返すべき出力パラメータには、NULL が設定されます。 NULL は、記憶領域が実際には割り当てられなかったことを示します。 このようなポインタに関連する長さフィールド (gss_buffer_desc 構造体を参照) は 0 に設定されます。 そのような場合、アプリケーションはこれらのバッファーを解放する必要はありません。
GSS-API における「流通」の基本単位は「トークン」です。 GSS-API を使用するアプリケーションは、トークンを使用して互いに通信します。 トークンは、データを交換したりセキュリティーを確立したりするために使われます。 トークンは gss_buffer_t データ型として宣言されます。 トークンはアプリケーションに対して不透明です。
トークンには、「コンテキストレベルトークン」と「メッセージ毎トークン」の 2 種類があります。 コンテキストレベルトークンは主に、コンテキストを確立する際、つまりコンテキストを起動して受け入れる際に使用されます。 コンテキストレベルトークンは、コンテキストを管理する目的で、後になって渡されることがあります。
メッセージ毎トークンは、コンテキストが確立されたあとで使用されます。 メッセージ毎トークンは、データ保護サービスを提供する目的で使用されます。 たとえば、別のアプリケーションにメッセージを送信したいアプリケーションを考えます。 そのアプリケーションは、GSS-API を使って暗号化識別子を生成し、それをメッセージに添付します。 その識別子はトークンに格納されます。
メッセージ毎トークンは、メッセージとの関係において次のように考えることができます。 「メッセージ」とは、アプリケーションがピアに送信するデータです。 たとえば、ls コマンドは、ftp サーバーに送信されるメッセージになりえます。 メッセージ毎トークンとは、そのメッセージに対して GSS-API が生成するオブジェクトのことです。 メッセージ毎トークンの例としては、暗号タグや暗号化された形式のメッセージが挙げられます。 ただし、後者の例は若干不正確です。 暗号化されたメッセージはやはりメッセージであり、トークンではありません。 トークンと呼べるのは、GSS-API によって生成された情報だけです。 しかし、正式にではありませんが、「メッセージ」と「メッセージ毎トークン」は同じ意味で使用されることがあります。
次の作業はアプリケーションの責任です。
トークンを送受信すること。 開発者はこのようなアクションを実行するために、通常、汎用的な読み取り関数と書き込み関数を作成する必要があります。 「その他の GSS-API 関数例」の send_token() 関数と recv_token() 関数を参照してください。
トークンの種類を区別し、それに応じてトークンを操作すること。
トークンはアプリケーションに対して不透明であるため、アプリケーションは、あるトークンと別のトークンを区別できません。 トークンの内容がわからなくても、アプリケーションはトークンの種類を区別できる必要があります。なぜなら、そうしないとトークンを適切な GSS-API 関数に渡せないからです。
アプリケーションは、次の方法でトークンの種類を区別できます。
状態によって (プログラムの制御フローを通じて)。 たとえば、コンテキストを受け入れるために待機しているアプリケーションは、受信したトークンはコンテキストの確立に関係するものであると仮定します。 ピアは、コンテキストが完全に確立されるまで、メッセージトークン (つまりデータ) の送信を行わないと予想されます。 いったんコンテキストが確立されると、アプリケーションは新しいトークンがメッセージトークンであると仮定します。 このようなトークンの処理方法は、非常に一般的なものです。 後述のプログラム例でもこの方法を使用しています。
フラグによって。 たとえば、トークンをピアに送信するための関数がアプリケーションに含まれている場合、そのアプリケーションにはトークンの種類を示すフラグを含めることができます。 次のコードを考えます。
gss_buffer_t token; /* declare the token */ OM_uint32 token_flag /* flag for describing the type of token */ <get token from a GSS-API function> token_flag = MIC_TOKEN; /* specify what kind of token it is */ send_a_token(&token, token_flag);
受信側のアプリケーションは、受信関数 (get_a_token() など) で token_flag 引数を検査します。
明示的なタグ付けによって。 アプリケーションは「メタトークン」を使用できます。 メタトークンは、GSS-API 関数から受け取ったトークンを格納するためのユーザー定義の構造体です。 メタトークンには、GSS-API から提供されたトークンの使用方法を示すユーザー定義フィールドが含まれます。
GSS-API では、マルチプロセスアプリケーション内のあるプロセスから別のプロセスにセキュリティーコンテキストを渡せます。 通常、アプリケーションはクライアントのコンテキストを受け入れます。 アプリケーションはそのコンテキストをアプリケーション内のプロセス間で共有します。 マルチプロセスアプリケーションについては、「GSS-API におけるコンテキストのエクスポートとインポート」を参照してください。
gss_export_context() 関数はプロセス間トークンを作成します。 このトークンに含まれる情報を使えば、2 番目のプロセス内でコンテキストを再構築できます。 あるプロセスから別のプロセスにプロセス間トークンを渡すのは、アプリケーションの責任です。 この状況は、トークンを別のアプリケーションに渡すのがアプリケーションの責任であることに似ています。
プロセス間トークンには、鍵などの機密情報が含まれる可能性があります。 必ずしもすべての GSS-API 実装がプロセス間トークンを暗号技術で保護するとは限りません。 したがって、アプリケーションは、プロセス間トークンに保護を施したあとで交換を実施する必要があります。 そうした保護は、gss_wrap() でトークンを暗号化するなどして実現します (ただし、暗号化が利用可能である場合)。
注 - 異なる GSS-API 実装間では、プロセス間トークンを転送できるとは限りません。