Generic Security Service Application Programming Interface (GSS-API) は、ピアとなるアプリケーションに送信されるデータを保護する方法をアプリケーションに提供します。接続は通常、あるマシン上のクライアントから別のマシン上のサーバーに対して行われます。この章では、次の内容について説明します。
GSS-API を使用すると、プログラマはセキュリティーの点で汎用的なアプリケーションを記述できます。開発者は、特定のプラットフォーム、セキュリティー機構、保護の種類、または転送プロトコル向けにセキュリティー実装をカスタマイズする必要はありません。GSS-API を使用すれば、プログラマはネットワークデータを保護する方法の詳細を知る必要がありません。GSS-API を使用するプログラムは、ネットワークセキュリティーに関する移植性が高くなります。この移植性が、Generic Security Service API の優れた特徴を示します。
GSS-API は、セキュリティーサービスを汎用的な方法で呼び出し元に提供するフレームワークです。次の図に示すように、GSS-API フレームワークは、Kerberos v5 や公開鍵技術など、基盤となるさまざまな機構や技術によって支えられています。
GSS-API の主な機能は、簡単に言うと次の 2 つです。
GSS–API は、セキュリティー「コンテキスト」を作成し、アプリケーション間でのデータの送受信は、このコンテキスト内で行うことができます。コンテキストは、2 つのアプリケーションが互いに信頼している状態を表します。コンテキストを共有するアプリケーションは、相手がだれであるかを知っており、したがって、そのコンテキストが継続する限り、互いにデータを転送できます。
GSS–API は、「セキュリティーサービス」として知られる 1 種類以上の保護機能を、転送データに対して適用します。セキュリティーサービスについては、「GSS-API のセキュリティーサービス」を参照してください。
さらに、GSS-API は次の機能を実行します。
データ変換
エラーの検査
ユーザー特権の委託
情報の表示
識別情報の比較
GSS-API にはさまざまな補助関数や簡易関数が含まれています。
GSS-API は、アプリケーションに対して次のような移植性を提供します。
機構非依存。GSS-API は汎用的なセキュリティーインタフェースを提供します。デフォルトのセキュリティー機構を指定することで、アプリケーションは適用すべき機構や機構の詳細を知る必要がなくなります。
プロトコル非依存。GSS-API は特定の通信プロトコルまたはプロトコル群に依存しません。たとえば、GSS–API は、ソケット、RCP、TCP/IP のいずれを使用するアプリケーションからも使用可能です。
RPCSEC_GSS は、GSS-API と RPC をスムースに統合するために追加される層です。詳細は、「リモートプロシージャー呼び出しと GSS-API」を参照してください。
プラットフォーム非依存。GSS-API は、アプリケーションが動作しているオペレーティングシステムの種類に依存しません。
保護品質非依存。保護品質 (Quality of Protection、QOP) とは、データを暗号化したり暗号タグを生成したりする際に使用されるアルゴリズムの種類を示します。GSS-API では、プログラマは QOP を無視できます。それには、GSS-API が提供するデフォルトを使用します。一方、必要であればアプリケーションは QOP を指定することもできます。
GSS-API は次の 3 種類のセキュリティーサービスを提供します。
認証 – 認証は、GSS-API によって提供される基本的なセキュリティー機能です。認証とは相手の身元を確認することです。あるユーザーが認証されると、システムは、そのユーザーがそのユーザー名で活動する権利を持つ人であるとみなします。
整合性 – 「整合性」は、データの有効性を検証することです。データが有効なユーザーから送られてきたとしても、そのデータ自体が破壊または改ざんされている可能性があります。整合性は、メッセージが完全に意図されたとおりの内容であり、情報の追加や削除がまったく行われていないことを保証します。GSS-API では、メッセージ整合性コード (Message Integrity Code、MIC) と呼ばれる暗号タグを、データに添付できるようになっています。MIC は、ユーザーが受信したデータが、送信側が送信したデータと同一であることを証明します。
機密性 – 「機密性」は、メッセージを傍受した第三者がその内容を読み取ろうとしても読み取れないことを保証します。認証と整合性のどちらも、データに変更を施すわけではありません。したがって、データが何らかの方法で傍受された場合、そのデータの内容が他人に読み取られてしまいます。したがって、GSS-API ではデータを暗号化できるようになっています。ただしそれには、暗号化をサポートする機構が利用可能である必要があります。このようにデータを暗号化することを機密性と呼びます。
GSS-API の現在の実装では、次の機構が利用できます。 Kerberos v5、Diffie-Hellman、および SPNEGO。Kerberos 実装についての詳細は、『Solaris のシステム管理 (セキュリティサービス)』の第 21 章「Kerberos サービスについて」を参照してください。Kerberos v5 は、GSS-API 対応プログラムが動作するすべてのシステム上で、インストールおよび実行される必要があります。
RPC (Remote Procedure Call) プロトコルをネットワークアプリケーションに使用するプログラマは、RPCSEC_GSS を使用してセキュリティーを提供できます。RPCSEC_GSS は GSS-API 上にある別の層です。RPCSEC_GSS は GSS-API のすべての機能を RPC 用にカスタマイズした形式で提供します。実際、RPCSC_GSS は GSS-API の多くの側面をプログラマから隠蔽する役割を果たしており、その結果、特に高い操作性と移植性を備えた RPC セキュリティーが実現されています。RPCSEC_GSS についての詳細は、『ONC+ 開発ガイド』の「RPCSEC_GSS を使用した認証」を参照してください。
次の図は、RPCSEC_GSS 層がアプリケーションと GSS-API の間に位置している様子を示したものです。
GSS-API は、データ保護作業を単純化しますが、GSS-API の一般的な性質に合致しないいくつかの作業をサポートしていません。GSS-API が実行「しない」作業は、次のとおりです。
ユーザーまたはアプリケーションにセキュリティー資格を提供すること。資格は、実際のセキュリティー機構が提供する必要があります。GSS-API は、アプリケーションが資格を自動的または明示的に獲得することを可能にしています。
アプリケーション間でデータを転送すること。セキュリティー関連のデータまたは通常のデータのどちらの場合でも、ピア間ですべてのデータの転送を処理することはアプリケーションの責任です。
転送データのさまざまな種類を識別すること。たとえば、GSS-API は、データパケットがプレーンデータ、暗号化データのいずれであるかを認識できません。
非同期エラーによる状態を示すこと。
マルチプロセスプログラムのプロセス間で送信される情報をデフォルトで保護すること。
GSS-API 関数に渡される文字列バッファーを割り当てること。「GSS-API の文字列とそれに類するデータ」を参照してください。
GSS-API データ領域を解放すること。そうしたメモリー領域の解放は、gss_release_buffer() や gss_delete_name() などの関数を使って明示的に行う必要があります。
このマニュアルでは現在、GSS-API の C 言語バインディング、つまり関数とデータ型だけに言及しています。Java バインディング版の GSS-API が利用可能になりました。Java GSS-API には、RFC 2853 で規定された Generic Security Services Application Program Interface (GSS-API) に対する Java バインディングが含まれています。
GSS-API についての詳細は、次の 2 つの文書を参照してください。
『Generic Security Service Application Program Interface』 (ftp://ftp.isi.edu/in-notes/rfc2743.txt) は、GSS-API の概念について簡単に説明しています。
『Generic Security Service API Version 2: C-Bindings』 (ftp://ftp.isi.edu/in-notes/rfc 2744.txt) は、C 言語ベースの GSS-API に固有の情報を提供しています。
ここでは、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-API 関数に渡す前に gss_buffer_desc 構造体に変換しておく必要があります。次の例では、汎用的な 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 構造体内に格納されています。
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)」と呼びます。機構名とは、特定の機構の名前ではなく、特定の機構によって生成された主体の名前です。このプロセスを示したのが次の図です。
サーバーがクライアントからある名前を受け取り、その名前をアクセス制御リスト内で検索する必要がある場合を考えます。「アクセス制御リスト (Access Control List、ACL)」とは、特定のアクセス権を持つ主体のリストのことです。そうした検索を行うには、次のような方法が考えられます。
gss_import_name() で、クライアント名を GSS-API 内部形式にインポートします (まだインポートされていない場合)。
場合によっては、サーバーは名前を内部形式で受け取ります。その場合、この手順は必要ありません。たとえば、サーバーはクライアント自身の名前を検索する可能性があります。コンテキストの起動中、クライアント自身の名前は内部形式で渡されます。
gss_import_name() で、各 ACL 名を インポートします。
次の図に、このプロセスを示します。ここでは手順 1 が必要であると仮定します。
名前の数が少ない場合は、名前を個別に比較する上記の方法でも問題ありません。名前の数が非常に多い場合は、gss_canonicalize_name() 関数を使用するほうが効率的です。この方法の実行手順を次に示します。
gss_import_name() で、クライアント名をインポートします (まだインポートされていない場合)。
名前を比較する前述の方法と同様に、名前がすでに内部形式である場合には、この手順は必要ありません。
gss_canonicalize_name() を使用してクライアント名の機構名バージョンを生成します。
gss_export_name() を使用してエクスポート名を生成します。エクスポート名は、連続した文字列としてのクライアント名です。
memcmp() を使用してエクスポートされたクライアント名を ACL 内の個々の名前と比較します。これは、高速で動作するオーバーヘッドの少ない関数です。
次の図に、このプロセスを示します。ここでも、サーバーがクライアントから受信した名前をインポートする必要があると仮定します。
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 を提供します。
typedef struct gss_OID_desc_struct { OM_uint32 length; void *elements; } gss_OID_desc, *gss_OID;
さらに、1 つ以上の OID を gss_OID_set_desc 構造体に格納することもできます。
typedef struct gss_OID_set_desc_struct { size_t count; gss_OID elements; } gss_OID_set_desc, *gss_OID_set;
アプリケーションは free() で OID を解放するべきではありません。
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 実装間では、プロセス間トークンを転送できるとは限りません。
ここでは、GSS-API を使用してセキュリティー保護されたデータ交換処理を実装する方法について説明します。ただし、GSS-API を使用するうえでもっとも中心となる関数に焦点を当てます。詳細は、付録 B GSS-API リファレンスを参照してください。この付録には、すべての GSS-API 関数、状態コード、およびデータ型の一覧が含まれています。各 GSS-API 関数についての詳細は、個々のマニュアルページを参照してください。
このマニュアルの例では単純なモデルを使用します。クライアントアプリケーションがリモートサーバーに直接データを送信します。RPC などのトランスポートプロトコル層による仲介は発生しません。
GSS-API を使用する際の一般的な手順は、次のとおりです。
各アプリケーション (送信側と受信側の両方) は資格を明示的に獲得します (資格を自動的に獲得していない場合)。
送信側はセキュリティーコンテキストを起動します。受信側はそのコンテキストを受け入れます。
送信側は転送するデータにセキュリティー保護を適用します。送信側はメッセージを暗号化するか、データに識別タグを付けます。その後、送信側は保護されたメッセージを転送します。
送信側はセキュリティー保護を適用しなくてもかまいません。その場合、デフォルトの GSS-API セキュリティーサービスである認証だけがメッセージに適用されます。
受信側はメッセージを復号化し (必要であれば)、メッセージを検証します (該当する場合)。
(省略可能) 確認のため、受信側は識別タグを送信側に返送します。
送信側と受信側のアプリケーションは両方とも共有セキュリティーコンテキストを無効にします。必要であれば、アプリケーションは残りの GSS-API データもすべて解放します。
割り当てられたすべてのデータ領域を解放することは、呼び出し元のアプリケーションの責任です。
GSS-API を使用するアプリケーションは、ファイル gssapi.h をインクルード (include) する必要があります。
「資格」とは、主体名に対するアプリケーションの要求の証明を提供するデータ構造です。アプリケーションは、資格を使って自身の大域アイデンティティーを確立します。さらに資格は、エンティティーの権限を確認する目的で使用される場合もあります。
GSS-API 自身は資格を提供しません。資格は、GSS-API 関数が呼び出される前に、GSS-API の背後にあるセキュリティー機構によって作成されます。多くの場合、ユーザーはログイン時に資格を受け取ります。
ある特定の GSS-API 資格は単一の主体に対してのみ有効です。単一の資格には、その主体に対する要素が複数個含まれる可能性があります。それらの要素は機構ごとに 1 つずつ作成されます。複数のセキュリティー機構を備えたマシン上で獲得した資格は、それらの機構のサブセットを備えたマシンに転送された場合に有効になります。GSS-API は gss_cred_id_t 構造体を通じて資格にアクセスします。この構造体のことを「資格ハンドル」と呼びます。資格はアプリケーションに対して不透明です。したがって、アプリケーションは与えられた資格の詳細を知る必要はありません。
資格には 3 つの形式があります。
セキュリティーコンテキストが確立できるようになるまでに、サーバーとクライアントはそれぞれの資格を獲得する必要があります。資格は有効期限が切れるまで何度でも使用できます。有効期限が切れると、アプリケーションは資格を獲得し直す必要があります。クライアントが使用する資格とサーバーが使用する資格とでは、その有効期間が異なる場合があります。
GSS-API ベースのアプリケーションが資格を獲得する方法には、次の 2 つがあります。
ほとんどの場合、gss_acquire_cred() を呼び出すのは、コンテキストの受け入れ側、つまりサーバーだけです。コンテキストの起動側、つまりクライアントは一般に、ログイン時に資格を受け取ります。したがって、クライアントは通常、デフォルトの資格を指定できます。サーバーは、gss_acquire_cred() を使用せずに、自身のデフォルトの資格を使用することもできます。
クライアントの資格は、そのクライアントの身元をほかのプロセスに対して証明します。サーバーが資格を獲得すると、セキュリティーコンテキストの受け入れが可能になります。したがって、クライアントがサーバーに ftp 要求を送る場合、そのクライアントはログイン時からすでに資格を獲得している可能性があります。クライアントがコンテキストを起動しようとすると、GSS-API は自動的にその資格を取得します。しかし、サーバープログラムは要求されたサービス (ftp) の資格を明示的に獲得します。
gss_acquire_cred() が正常終了すると、GSS_S_COMPLETE が返されます。有効な資格を返せない場合は、GSS_S_NO_CRED が返されます。その他のエラーコードについては、gss_acquire_cred(3GSS) のマニュアルページを参照してください。使用例については、第 6 章の「資格の獲得」を参照してください。
gss_add_cred() は gss_acquire_cred() に似ています。しかし、gss_add_cred() を使用すれば、アプリケーションは既存の資格を基に新しいハンドルを作成したり、既存の資格に新しい資格要素を追加したりできます。GSS_C_NO_CREDENTIAL
を既存の資格として指定した場合、gss_add_cred() はデフォルトの動作に従って新しい資格を作成します。詳細は、gss_add_cred(3GSS) のマニュアルページを参照してください。
GSS-API がセキュリティー提供時に行うもっとも重要な作業は、セキュリティーコンテキストを作成することと、データを保護することの 2 つです。アプリケーションは必要な資格を獲得したあと、セキュリティーコンテキストを確立する必要があります。コンテキストを確立するには、一方のアプリケーション (通常はクライアント) がコンテキストを起動し、もう一方のアプリケーション (通常はサーバー) がそのコンテキストを受け入れます。ピア間で複数のコンテキストが存在してもかまいません。
通信中のアプリケーションは、認証トークンを交換することによって、結合セキュリティーコンテキストを確立します。セキュリティーコンテキストは、2 つのアプリケーション間で共有すべき情報が入っている一対の GSS-API データ構造体です。この情報は、各アプリケーションのセキュリティーにおける状態を記述します。セキュリティーコンテキストはデータの保護のために必要です。
アプリケーションとリモートピア間でセキュリティーコンテキストを起動するには、gss_init_sec_context() 関数が使用されます。処理が成功すると、この関数は、確立すべきコンテキストの「コンテキストハンドル」と、受け入れ側に送信すべきコンテキストレベルトークンを返します。gss_init_sec_context() を呼び出す前に、クライアントは次の作業を行う必要があります。
必要であれば、gss_acquire_cred() で資格を獲得します。通常の場合、クライアントはログイン時に資格を受け取ります。gss_acquire_cred() は単純に、実行中のオペレーティングシステムから初期の資格を取得できます。
gss_import_name() で、サーバー名を GSS-API 内部形式にインポートします。名前と gss_import_name() についての詳細は、「GSS-API における名前」を参照してください。
gss_init_sec_context() を呼び出す際、クライアントは通常、次の引数値を渡します。
GSS_C_NO_CREDENTIAL
を引数 cred_handle に渡して、デフォルトの資格を示します
GSS_C_NULL_OID
を引数 mech_type に渡して、デフォルトの機構を示します
GSS_C_NO_CONTEXT
を引数 context_handle に渡して、初期コンテキストが空であることを示します。gss_init_sec_context() は通常ループ内で呼び出されるため、後続の呼び出しは以前の呼び出しで戻されたコンテキストハンドルを渡す必要があります
GSS_C_NO_BUFFER
を引数 input_token に渡して、トークンが最初は空であることを示します。あるいは、アプリケーションは length フィールドが 0 に設定されている gss_buffer_desc オブジェクトへのポインタを渡すこともできます
gss_import_name() で GSS-API 内部形式にインポートされたサーバー名を渡します
アプリケーションは必ずしもこのようなデフォルト値を使用する必要はありません。さらに、クライアントは引数 req_flags を使用して、他のセキュリティーパラメータに対する要件を指定することもできます。gss_init_sec_context() の引数についての詳細は、以降の節で説明します。
コンテキスト受け入れ側は、コンテキスト確立時にいくつかのハンドシェークを要求する可能性があります。つまり、受け入れ側は、コンテキストが完全に確立されるまで、複数のコンテキスト情報を送信するように起動側に要求できます。したがって、移植性のため、コンテキストの起動は常に、コンテキストが完全に確立されたかどうかを検査するループの一部として行われる必要があります。
コンテキストが完全に確立されていない場合、gss_init_sec_context() はメジャー状態コードとして GSS_C_CONTINUE_NEEDED を戻します。したがって、その gss_init_sec_context() からの戻り値を使用して、起動ループを継続するかどうかの判定を行う必要があります。
クライアントはコンテキスト情報をサーバーに、gss_init_sec_context() から戻された「出力トークン」の形式で渡します。クライアントは、サーバーから情報を「入力トークン」として受け取ります。その後、その入力トークンは、後続の gss_init_sec_context() 呼び出しの引数として渡すことができます。受け取った入力トークンの長さが 0 の場合、サーバーはこれ以上出力トークンを要求していないことが分かります。
したがって、ループ内で gss_init_sec_context() の戻り値の状態を検査する以外に、入力トークンの長さも検査する必要があります。その長さが 0 以外の値である場合、別のトークンをサーバーに送信する必要があります。ループを開始する前に、入力トークンの長さを 0 に初期化しておく必要があります。入力トークンを GSS_C_NO_BUFFER
に設定するか、その構造体の length フィールドの値を 0 に設定します。
次の擬似コードは、クライアント側からのコンテキストの確立例を示したものです。
コンテキストを GSS_C_NO_CONTEXT で初期化する 入力トークンを GSS_C_NO_BUFFER で初期化する do call gss_init_sec_context(資格, コンテキスト, 名前, 入力トークン, 出力トークン, その他の引数...) if (受け入れ側に送信すべき出力トークンが存在する) 受け入れ側に出力トークンを送信する 出力トークンを解放する if (コンテキストが完全でない) 受け入れ側から入力トークンを受信する if (GSS-API エラーが発生した) コンテキストを削除する until コンテキストが完成
実際のループは、さまざまなエラー検査を含んだ、より複雑なものになります。そうしたコンテキスト起動ループの実際の例については、「サーバーとのセキュリティーコンテキストの確立」を参照してください。さらに、gss_init_sec_context(3GSS) のマニュアルページにも、上記例ほど一般化されていない例があります。
一般に、コンテキストが完全に確立されていない時に戻されるパラメータ値は、コンテキストが完了した時に戻されるはずの値です。詳細は、gss_init_sec_context() のマニュアルページを参照してください。
gss_init_sec_context() が正常終了すると、GSS_S_COMPLETE が返されます。コンテキスト確立トークンがピアとなるアプリケーションから要求された場合、GSS_S_CONTINUE_NEEDED が返されます。エラーが発生した場合、gss_init_sec_context(3GSS) のマニュアルページに記載されたエラーコードが返されます。
コンテキストの起動が失敗した場合、クライアントはサーバーから切断する必要があります。
コンテキストの確立におけるもう 1 つの仕事は、コンテキストの受け入れです。コンテキストの受け入れは gss_accept_sec_context() 関数で行います。通常の場合、クライアントが gss_init_sec_context() で起動したコンテキストを、サーバーが受け入れます。
gss_accept_sec_context() への主な入力は、起動側から受け取った入力トークンです。gss_accept_sec_context は、コンテキストハンドルと起動側に戻すべき出力トークンを戻します。しかし、gss_accept_sec_context() を呼び出す前に、サーバーはクライアントから要求されたサービスの資格を獲得しておく必要があります。サーバーはこのような資格を gss_acquire_cred() 関数で獲得します。あるいは、サーバーは、資格を明示的に獲得するのではなく、gss_accept_sec_context() を呼び出す際にデフォルトの資格 (GSS_C_NO_CREDENTIAL) を指定することもできます。
gss_accept_sec_context() を呼び出すとき、サーバーは次の引数を設定できます。
cred_handle – gss_acquire_cred() によって返された資格ハンドル。あるいは、デフォルトの資格を示す GSS_C_NO_CREDENTIAL
も使用できます。
context_handle – GSS_C_NO_CONTEXT
は初期コンテキストが空であることを示します。gss_init_sec_context() は通常ループ内で呼び出されるため、後続の呼び出しは以前の呼び出しで戻されたコンテキストハンドルを渡す必要があります。
input_token – クライアントから受け取ったコンテキストトークン。
gss_accept_sec_context() 引数についての詳細は、以降の節で説明します。
セキュリティーコンテキストを確立するためには、いくつかのハンドシェークが必要となる可能性があります。コンテキストが完全に確立されるまでに、起動側と受け入れ側は通常、複数のコンテキスト情報を送信する必要があります。したがって、移植性のため、コンテキストの受け入れは常に、コンテキストが完全に確立されたかどうかを検査するループの一部として行われる必要があります。コンテキストがまだ確立されていない場合、gss_accept_sec_context() はメジャー状態コード GSS_C_CONTINUE_NEEDED を返します。したがって、ループは gss_accept_sec_context() の戻り値を使用して、受け入れループを継続するかどうかを判定する必要があります。
コンテキスト受け入れ側はコンテキスト情報をコンテキスト起動側に、gss_accept_sec_context() から戻された出力トークンの形式で渡します。その後、受け入れ側は、起動側から追加情報を入力トークンとして受け取れます。入力トークンは、後続の gss_accept_sec_context() 呼び出しの引数として渡されます。起動側に送信すべきトークンがなくなると、gss_accept_sec_context() から長さ 0 の出力トークンが返されます。ループ内で、gss_accept_sec_context() の戻り値の状態を検査する以外に、出力トークンの長さを検査して、別のトークンを送信すべきかどうかを判断する必要があります。ループを開始する前に、出力トークンの長さを 0 に初期化しておく必要があります。出力トークンを GSS_C_NO_BUFFER
に設定するか、その構造体の length フィールドの値を 0 に設定します。
次の擬似コードは、サーバー側からのコンテキストの確立例を示したものです。
コンテキストを GSS_C_NO_CONTEXT で初期化する 出力トークンを GSS_C_NO_BUFFER で初期化する do 起動側から入力トークンを受信する call gss_accept_sec_context(コンテキスト, 資格ハンドル, 入力トークン, 出力トークン, その他の引数...) if (起動側に送信すべき出力トークンが存在する) 起動側に出力トークンを送信する 出力トークンを解放する if (GSS-API エラーが発生した) コンテキストを削除する until コンテキストが完成
実際のループは、さまざまなエラー検査を含んだ、より複雑なものになります。そうしたコンテキスト受け入れループの実際の例については、「サーバーとのセキュリティーコンテキストの確立」を参照してください。さらに、gss_accept_sec_context() のマニュアルページにも例が記載されています。
繰り返しになりますが、GSS-API 自身はトークンを送受信しません。トークンの送受信はアプリケーションが処理する必要があります。トークンを転送する関数の例については、「その他の GSS-API 関数例」を参照してください。
正常に終了した場合、gss_accept_sec_context() は GSS_S_COMPLETE を戻します。コンテキストが完全に確立されていない場合、gss_accept_sec_context は GSS_S_CONTINUE_NEEDED を戻します。エラーが発生した場合、この関数はエラーコードを戻します。詳細は、gss_accept_sec_context(3GSS) のマニュアルページを参照してください。
gss_init_sec_context() 関数では、アプリケーションは、基本的なコンテキストの確立以外に、追加のデータ保護サービスを要求できるようになっています。このようなサービスを要求するには、gss_init_sec_context() の req_flags 引数を使用します。
すべての機構がこれらすべてのサービスを提供するわけではありません。gss_init_sec_context() の ret_flags 引数は、指定されたコンテキストでどのサービスが利用できるかを示します。同様に、コンテキスト受け入れ側では、gss_accept_sec_context() が返す ret_flags 値から、どのサービスを利用できるかを判断します。以降の節では、追加のサービスについて説明します。
許可されていれば、コンテキスト起動側はコンテキスト受け入れ側が代理として動作するように要求できます。そのような場合、受け入れ側は、起動側に代わって別のコンテキストを起動できます。
マシン A 上のあるユーザーが、マシン B に rlogin したあと、さらにマシン B からマシン C に rlogin したいものとします。このとき、委託された資格は B を A として識別するか、あるいは A のプロキシとして動作している B として識別するかは、機構によって異なります。
委託が許可されると、ret_flags に値 GSS_C_DELEG_FLAG を設定できます。受け入れ側は委託された資格を gss_accept_sec_context() の delegated_cred_handle 引数として受け取ります。資格の委託はコンテキストのエクスポートとは異なります。「GSS-API におけるコンテキストのエクスポートとインポート」を参照してください。その違いの 1 つは、アプリケーションの資格は一度に複数回委託できますが、コンテキストは一度に 1 つのプロセスでしか保持できない、という点です。
ftp サイトにファイルを転送するユーザーは通常、そのサイトの身元についての証明を必要としません。これに対し、アプリケーションからクレジットカード番号の提供を求められたユーザーは、その受信側の身元についての確実な証明を得たいはずです。そうした場合に「相互認証」が必要になります。コンテキストの起動側と受け入れ側の両方が、自身の身元を証明する必要があります。
コンテキスト起動側が相互認証を要求するには、gss_init_sec_context() の req_flags 引数に値 GSS_C_MUTUAL_FLAG
を設定します。相互認証が承認されると、この関数はそのことを示すために、ret_flags 引数にこの値を設定します。相互認証が要求されたが使用できない場合、適切な対処を行うのは起動側のアプリケーションの責任です。相互認証が要求されたが使用できない場合でも、GSS-API は自動的にコンテキストを終了しません。また、機構の中には、特に要求がなくても相互認証を常に実行するものもあります。
GSS-API の通常の使用においては、起動側の識別情報はコンテキスト確立の一部として、受け入れ側で使用できるようになります。しかし、コンテキスト起動側は自身の識別情報をコンテキスト受け入れ側に知らせないように要求することもできます。
たとえば、医療データベースへの無制限のアクセスを提供するアプリケーションを考えます。そのようなサービスのクライアントは、サービスの認証を要求することが考えられます。この方法では、データベースから取り出されるすべての情報に対して信頼が確立されます。しかし、クライアントはプライバシー上の理由などにより、識別情報を公開したくないかもしれません。
匿名性を要求するには、gss_init_sec_context() の req_flags 引数に GSS_C_ANON_FLAG を設定します。匿名性が利用可能かどうかを検査するには、gss_init_sec_context () または gss_accept_sec_context() の ret_flags 引数に GSS_C_ANON_FLAG が返されるかどうかを確認します。
匿名性が有効である場合、gss_accept_sec_context() または gss_inquire_context() から返されたクライアント名を指定して gss_display_name()を呼び出すと、汎用的な匿名が生成されます。
匿名性が要求されたが使用できない場合、適切な対処を行うのはアプリケーションの責任です。GSS-API はそのような場合にコンテキストを終了しません。
多くのアプリケーションでは、基本的なコンテキスト確立を行うだけで、コンテキスト起動側を適切に認証できます。追加のセキュリティーが必要な場合、GSS-API ではチャネルバインディングを使用します。チャネルバインディングとは、使用されている特定のデータチャネルを識別するためのタグのことです。具体的には、チャネルバインディングはコンテキストの起点と終点 (つまり起動側と受け入れ側) を識別します。これらのタグは起動側と受け入れ側のアプリケーションに固有であるため、識別情報のより有効な証明となります。
チャネルバインディングは、次に示すように、gss_channel_bindings_struct 構造体へのポインタである gss_channel_bindings_t データ型によって示されます。
typedef struct gss_channel_bindings_struct { OM_uint32 initiator_addrtype; gss_buffer_desc initiator_address; OM_uint32 acceptor_addrtype; gss_buffer_desc acceptor_address; gss_buffer_desc application_data; } *gss_channel_bindings_t;
最初の 2 つのフィールドは起動側のアドレスとアドレス型 (起動側のアドレスが送信される形式) を示します。たとえば、initiator_addrtype を GSS_C_AF_INET
に設定した場合、initiator_address がインターネットアドレス形式 (つまり IP アドレス) であることを示します。同様に、3 番目と 4 番目のフィールドは受け入れ側のアドレスとアドレス型を示します。最後のフィールド application_data は、アプリケーションが自由に使用することができます。application_data を使用する予定がない場合、このフィールドを GSS_C_NO_BUFFER
に設定します。アプリケーションがアドレスを指定しない場合、アドレス型フィールドを GSS_C_AF_NULLADDR
に設定します。有効なアドレス型の値については、「チャネルバインディングのアドレス型」を参照してください。
アドレス型は、特定のアドレス形式を示すのではなく、アドレスファミリを示します。アドレスファミリが複数の代替アドレス形式を持つ場合、どの形式を使用しているかを判断できるだけの十分な情報を、initiator_address と acceptor_address のフィールドに指定する必要があります。特に指定しない限り、アドレスはネットワークのバイト順 (つまり、アドレスファミリにネイティブなバイト順) で指定します。
チャネルバインディングを使用してコンテキストを確立するには、割り当てられたチャネルバインディング構造体を gss_init_sec_context() の input_chan_bindings 引数で指します。この構造体の各フィールドが連結されてオクテット文字列が生成され、そこから MIC が派生されます。次に、この MIC が出力トークンに添付されます。続いて、アプリケーションはそのトークンをコンテキスト受け入れ側に送信します。トークンを受け取った受け入れ側は、gss_accept_sec_context() を呼び出します。詳細は、「GSS-API におけるコンテキストの受け入れ」を参照してください。gss_accept_sec_context() は、受け取ったチャネルバインディングの MIC を計算します。gss_accept_sec_context() は、MIC が一致しない場合に GSS_C_BAD_BINDINGS を返します。
gss_accept_sec_context() からは転送されたチャネルバインディングが返されるため、受け入れ側は、それらの値に基づいてセキュリティー検査を実行できます。たとえば application_data の値をセキュアデータベースに保存しておいたコードワードと比較したりできます。
チャネルバインディング情報の機密性を提供するかどうかは、実際の機構によって異なります。したがって、アプリケーションは、機密性が保証されるまで、チャネルバインディングに機密情報を含めてはなりません。機密性が利用可能かどうかを判断するには、アプリケーション内で gss_init_sec_context() または gss_accept_sec_context() の ret_flags 引数を検査します。値 GSS_C_CONF_FLAG と GSS_C_PROT_READY_FLAG が機密性を示します。ret_flags については、「GSS-API におけるコンテキストの起動」または 「GSS-API におけるコンテキストの受け入れ」を参照してください。
機構はそれぞれ、チャネルバインディングにおけるアドレスとアドレス型に追加の制限を課すことができます。たとえば、機構は、チャネルバインディングの initiator_address フィールドが gss_init_sec_context() に返されるかどうかを検証したりできます。したがって、アプリケーションの移植性を高めるには、アドレスフィールドに正しい情報を設定する必要があります。正しい情報を決定できない場合は、GSS_C_AF_NULLADDR
をアドレス型に指定する必要があります。
GSS-API は、コンテキストをエクスポートおよびインポートする方法を提供します。この機能を使えば、マルチプロセスアプリケーション (通常はコンテキスト受け入れ側) は、あるプロセスから別のプロセスにコンテキストを転送できます。たとえば、受け入れ側に、コンテキスト起動側からの応答を待つプロセスと、コンテキストに送信されたデータを使用するプロセスが存在する可能性があります。「test_import_export_context() 関数の使用」では、これらの関数を使ってコンテキストを保存および復元する方法について説明しています。
関数 gss_export_sec_context() は、エクスポートされるコンテキストに関する情報が入ったプロセス間トークンを作成します。詳細は、「GSS-API におけるプロセス間トークン」を参照してください。gss_export_sec_context() を呼び出す前に、トークンを受信するバッファーを GSS_C_NO_BUFFER
に設定する必要があります。
次に、アプリケーションはそのトークンをほかのプロセスに渡します。新しいプロセスはそのトークンを受け入れ、それを gss_import_sec_context() に渡します。多くの場合、アプリケーション間でトークンを渡すときに使用される関数が、プロセス間でトークンを渡すときにも使用されます。
セキュリティープロセスのインスタンスは一度に 1 つしか存在できません。gss_export_sec_context() はエクスポートされたコンテキストを無効にし、そのコンテキストハンドルを GSS_C_NO_CONTEXT
に設定します。また、gss_export_sec_context() は、そのコンテキストに関連付けられたプロセス内のすべてのリソースも解放します。コンテキストのエクスポートを完了できない場合、gss_export_sec_context() は、既存のセキュリティーコンテキストを元のまま残し、プロセス間トークンも返しません。
すべての機構でコンテキストをエクスポートできるわけではありません。アプリケーションでは、gss_accept_sec_context() または gss_init_sec_context() の ret_flags 引数をチェックして、コンテキストをエクスポートできるかどうかを判定できます。このフラグに GSS_C_TRANS_FLAG
が設定されている場合、コンテキストはエクスポートできます。「GSS-API におけるコンテキストの受け入れ」と 「GSS-API におけるコンテキストの起動」を参照してください。
図 4–6 に、マルチプロセスの受け入れ側がコンテキストをエクスポートしてマルチタスクを実現している様子を示します。この例では、プロセス 1 はトークンを受け取って処理します。このステップにより、コンテキストレベルトークンとデータトークンが分離され、それらのトークンがプロセス 2 に渡されます。プロセス 2 はアプリケーション固有の方法でデータを処理します。この図では、クライアントはすでに gss_init_sec_context() からエクスポートトークンを取得しています。クライアントはトークンをユーザー定義関数 send_a_token() に渡します。send_a_token は、転送するトークンがコンテキストレベルトークンまたはメッセージトークンのどちらであるかを示します。send_a_token() はトークンをサーバーに転送します。この図には示されていませんが、おそらく、send_a_token() はスレッド間でトークンを渡すときにも使用されます。
GSS-API は、指定されたセキュリティーコンテキストについての情報を取得する関数 gss_inquire_context(3GSS) を提供します。コンテキストは完全でなくてもかまわない点に注意してください。コンテキストハンドルを指定すると、gss_inquire_context() はそのコンテキストについて次の情報を提供します。
コンテキスト起動側の名前
コンテキスト受け入れ側の名前
コンテキストが有効である時間 (秒)
コンテキストで使用されるセキュリティー機構
いくつかのコンテキストパラメータフラグ。これらのフラグは gss_accept_sec_context(3GSS) 関数の ret_flags 引数と同じです。これらのフラグは、委託や相互認証などを請け負います。「GSS-API におけるコンテキストの受け入れ」を参照してください。
照会元のアプリケーションがコンテキスト起動側であるかどうかを示すフラグ
コンテキストが完全に確立されているかどうかを示すフラグ
2 つのピア間でコンテキストが確立されたあと、メッセージを送信する前にそのメッセージを保護できます。
コンテキストの確立時に使用されるのは、もっとも基本的な GSS-API 保護である 「認証」だけです。実際のセキュリティー機構によって異なりますが、GSS-API は次の 2 つの保護も提供します。
整合性 – gss_get_mic() 関数によってメッセージに対するメッセージ整合性コード (MIC) が生成されます。受信側は、MIC を検査することで、受信したメッセージが送信されたメッセージと同じかどうかを確認できます。
機密性 – MIC の使用に加え、メッセージが暗号化されます。暗号化を実行するのは、GSS-API の gss_wrap() 関数です。
gss_get_mic() と gss_wrap() の違いを、次の図に示します。gss_get_mic() を使用した場合、受信側はメッセージが変更されていないことを示すタグを受け取ります。gss_wrap() を使用した場合、受信側は暗号化されたメッセージとタグを受け取ります。
使用すべき関数はユーザーの状況に応じて異なります。gss_wrap() は整合性サービスも含むため、多くのプログラムは gss_wrap() を使用します。プログラムは、機密性サービスが利用可能かどうかを判定できます。続いてプログラムは、その利用可能性に応じて、機密性を指定して、あるいは指定しないで、gss_wrap()を呼び出すことができます。使用例については、「メッセージのラップと送信」を参照してください。ただし、gss_get_mic() を使用するとメッセージをラップ解除する必要がないため、gss_wrap() を使用する場合よりも CPU サイクルを節約できます。したがって、機密性が必要ないプログラムは 、gss_get_mic() でメッセージを保護する可能性があります。
gss_get_mic() を使用すると、プログラムは暗号化 MIC をメッセージに追加できます。受信側は、gss_verify_mic() でメッセージの MIC を検査できます。
gss_get_mic() は gss_wrap() とは対照的に、メッセージと MIC を別々に出力します。この分離は、送信側アプリケーションがメッセージと対応する MIC の両方を送信する必要があることを意味します。さらに重要なのは、受信側がメッセージと MIC を区別できる必要がある、という点です。メッセージと MIC を適切に処理するには、次のいずれかの方法を使用します。
プログラム制御 (つまり、状態) を通じて。受信側アプリケーションは受信関数を 2 回呼び出す (つまり、1 回目はメッセージを取得するため、2 回目はメッセージの MIC を取得するため) ことをあらかじめ知ることができます。
フラグを通じて。送信側と受信側は、どの種類のトークンを含めるかをフラグで示すことができます。
メッセージと MIC の両方を含むユーザー定義トークン構造体を通じて。
gss_get_mic() が正常終了すると、GSS_S_COMPLETE が返されます。指定された QOP が有効でない場合、GSS_S_BAD_QOP が返されます。詳細は、gss_get_mic(3GSS) を参照してください。
メッセージは、gss_wrap() 関数でラップすることが可能です。gss_get_mic() と同様に、gss_wrap() は MIC を提供します。また、機密性が要求され、かつ実際の機構で利用可能である場合には、gss_wrap() は指定されたメッセージの暗号化も行います。メッセージの受信側は gss_unwrap() でメッセージをラップ解除します。
gss_wrap() は gss_get_mic() とは違い、メッセージと MIC をいっしょにラップし、1 つの出力メッセージにします。このバンドルを送信する関数の呼び出しは、1 回だけですみます。これに対し、gss_unwrap() はメッセージを抽出します。MIC はアプリケーションからは見えません。
メッセージが正常にラップされた場合、gss_wrap() は GSS_S_COMPLETE を戻します。要求された QOP が有効でない場合、GSS_S_BAD_QOP が返されます。gss_wrap() の使用例については、「メッセージのラップと送信」を参照してください。
gss_wrap() でメッセージをラップすると、送信すべきデータのサイズが増加します。保護されたメッセージパケットは、指定された転送プロトコルを通過するのに適したサイズである必要があります。したがって、GSS-API は関数 gss_wrap_size_limit() を提供しています。 gss_wrap_size_limit() は、プロトコルにとって大きすぎないサイズにラップ可能なメッセージの最大サイズを計算します。この最大サイズを超える場合、アプリケーションは gss_wrap() を呼び出す前にメッセージを分割できます。メッセージを実際にラップする前にラップサイズの制限値を必ず検査してください。
変形を行うためにどの QOP アルゴリズムを使用するか
機密性を呼び出すかどうか
デフォルトの QOP は、GSS-API の実装ごとに異なる可能性があります。したがって、デフォルトの QOP を指定した場合でも、ラップ後のメッセージのサイズが異なる可能性があります。この可能性を示したのが次の図です。
機密性を適用するかどうかにかかわらず、gss_wrap() によってメッセージのサイズが増加します。gss_wrap() は、転送メッセージ内に MIC を埋め込みます。しかし、メッセージを暗号化すると (機密性を適用すると)、メッセージのサイズはさらに増加します。このプロセスを示したのが次の図です。
gss_wrap_size_limit() が正常終了すると、GSS_S_COMPLETE が返されます。指定された QOP が有効でない場合、GSS_S_BAD_QOP が返されます。gss_wrap_size_limit() で元のメッセージの最大サイズを求める例については、「メッセージのラップと送信」を参照してください。
この呼び出しが正常に終了したとしても、gss_wrap() が max-input-size バイトの長さを持つメッセージを必ず保護できるという保証はありません。この機能は、gss_wrap() の呼び出し時点で必要なシステムリソースが利用可能かどうかに依存します。詳細は、gss_wrap_size_limit(3GSS) のマニュアルページを参照してください。
コンテキスト起動側がコンテキスト受け入れ側に一連のデータパケットを順次転送する際、一部の機構では、その順序が正しいかどうかをコンテキスト受け入れ側が検査できるようになっています。これらの検査には、「パケットが正しい順序で到着したか」、「パケットが不必要に重複していないか」が含まれます。次の図を参照してください。受け入れ側がこれら 2 つの状態を検査するのは、パケットの検証時とパケットのラップ解除時です。詳細は、「メッセージのラップ解除」を参照してください。
起動側は gss_init_sec_context() で順序を検査できます。それには、GSS_C_REPLAY_FLAG
または GSS_C_SEQUENCE_FLAG
を論理 OR で req_flags 引数に設定します。
受信側は、転送メッセージのラップ解除後または検証後に、確認を送信側に返すことができます。つまり、そのメッセージの MIC を返送します。送信側がラップはしなかったが gss_get_mic() で MIC をタグ付けしているメッセージの場合を考えます。実行手順 (図 4–9) は次のようになります。
起動側は gss_get_mic() でメッセージにタグ付けします。
起動側はメッセージと MIC を受け入れ側に送信します。
受け入れ側は gss_verify_mic() でメッセージを検証します。
受け入れ側は MIC を起動側に返送します。
起動側は gss_verify_mic() で、元のメッセージに対して受信した MIC を検証します。
ラップされたデータの場合、gss_unwrap() 関数はメッセージと MIC を別々に生成しません。したがって、受信側は、受信した (およびラップ解除した) メッセージから MIC を生成する必要があります。実行手順 (図 4–10) は次のようになります。
起動側は gss_wrap() でメッセージをラップします。
起動側はラップしたメッセージを送信します。
受け入れ側は gss_unwrap() でメッセージをラップ解除します。
受け入れ側は gss_get_mic() でラップ解除されたメッセージの MIC を生成します。
受け入れ側は抽出した MIC を起動側に返信します。
起動側は gss_verify_mic() で、元のメッセージに対して受信した MIC を検証します。
アプリケーションは、GSS-API データ用に割り当てられたすべてのデータ領域を解放する必要があります。これに関係する関数は、gss_release_buffer(3GSS)、gss_release_cred(3GSS)、gss_release_name(3GSS)、および gss_release_oid_set(3GSS) です。
最終的に、すべてのメッセージの送受信が完了し、起動側と受け入れ側のアプリケーションが終了します。この時点で、両アプリケーションは gss_delete_sec_context() を呼び出して共有コンテキストを破棄する必要があります。gss_delete_sec_context() はコンテキストに関連するローカルのデータ構造体を削除します。
用心のため、アプリケーションは GSS-API データ用に割り当てたデータ領域をすべて解放するべきです。このような関数には、gss_release_buffer()、gss_release_cred()、gss_release_name()、および gss_release_oid_set() があります。