ONC+ 開発ガイド

認証

RPC のクライアントとサーバーを作成するときにさまざまなトランスポートを指定できるように、RPC クライアントにもさまざまなタイプの認証メカニズムを採用できます。RPC の認証サブシステムはオープンエンド型です。したがって、認証はさまざな使用法がサポートされます。認証プロトコルは、付録 B 「RPC プロトコルおよび言語の仕様」 で詳細に定義されています。

次の表で、RPC が現在サポートしている認証タイプを示します。

表 5–1 RPC が現在サポートしている認証タイプ

メソッド 

説明 

AUTH_NONE

デフォルト。認証は実行されない 

AUTH_SYS

UNIX オペレーティングシステムのプロセスアクセス権を基にした認証タイプ 

AUTH_SHORT

サーバーによっては効率向上のため AUTH_SYS の代わりに AUTH_SHORT を使用できる。AUTH_SYS 認証を使用するクライアントプログラムは、サーバーからの AUTH_SHORT 応答ベリファイアを受信できる。詳細は、 付録 B 「RPC プロトコルおよび言語の仕様」 を参照してください。

AUTH_DES

DES 暗号化技法を基にした認証タイプ 

AUTH_KERB

DES フレームワークを基にした Version 5 Kerberos 認証形式 

呼び出し側が次の方法で RPC クライアントハンドルを新規作成する場合を考えます。

clnt = clnt_create(host, prognum, versnum, nettype);

この場合対応するクライアント作成ルーチンが次のように認証ハンドルを設定します。

clnt->cl_auth = authnone_create();
 
 

新たな認証インスタンスを作成するときは、auth_destroy(clnt->cl_auth) を使用して現在のインスタンスを破棄します。この操作はメモリーの節約のために必 要です。

サーバー側では、RPC パッケージがサービスディスパッチルーチンに、任意の認証スタイルが結合されている要求を渡します。サービスディスパッチルーチンに渡された要求ハンドルには、rq_cred という構造体が入っています。その構成は、認証資格タイプを示すフィールドを除いて、ユーザーから隠されています。

/*
 * 認証データ
 */
struct opaque_auth {
   enum_t    oa_flavor;		/* 資格スタイル */
   caddr_t   oa_base;			/* より詳細な認証データのアドレス */
   u_int     oa_length; 	/* 最大 MAX_AUTH_BYTES まで */
};

RPC パッケージでは、サービスディスパッチルーチンに対して次のことを保証しています。

AUTH_SYS タイプの認証

クライアント側で AUTH_SYS (旧バージョンでは AUTH_UNIX) タイプの認証を使用 するには、RPC クライアントハンドルの作成後に clnt–>cl_auth を次のように設定します。

clnt->cl_auth = authsys_create_default();

以降は、この clnt を使用した RPC 呼び出しでは、次に示す資格 - 認証構造体が渡されます。


例 5–7 AUTH_SYS タイプの資格 - 認証構造体

/*
 * AUTH_SYS タイプの資格
 */
struct authsys_parms {
	u_long aup_time;		/* 資格作成時刻 */
 char *aup_machname;			/* クライアント側のホスト名 */
	uid_t aup_uid;	 		/* クライアント側の実効 uid */
	gid_t aup_gid;			/* クライアント側の現在のグループ ID */
	u_int aup_len;			/* aup_gids の配列の長さ */
 gid_t *aup_gids;			/* ユーザーが所属するグループの配列 */
};

rpc.broadcast では、デフォルトで AUTH_SYS タイプの認証になります。

次に、手続きを使用し、ネットワーク上のユーザー数を返すサーバープログラムである RUSERPROC_1 () を示します。認証の例として AUTH_SYS タイプの資格をチェックし、呼び出し側の uid16 の場合は要求に応じないようにしてあります。


例 5–8 認証データをチェックするサーバープログラム

nuser(rqstp, transp)
	struct svc_req *rqstp;
 SVCXPRT *transp;
{
	struct authsys_parms *sys_cred;
 uid_t uid;
	unsigned int nusers;
 
	/* NULLPROC の場合は認証データなし */
	if (rqstp->rq_proc == NULLPROC) {
 	if (!svc_sendreply( transp, xdr_void, (caddr_t) NULL))
		 fprintf(stderr, "can't reply to RPC call\n");
		return;
 }
 
	/* ここで uid を取得 */
	switch(rqstp->rq_cred.oa_flavor) {
		case AUTH_SYS:
			sys_cred = (struct authsys_parms *) rqstp->rq_clntcred;
			uid = sys_cred->aup_uid;
 		break;
		default:
			svcerr_weakauth(transp);
 		return;
	}
	switch(rqstp->rq_proc) {
 	case RUSERSPROC_1:
		/* 呼び出し側が、この手続きの呼び出し資格を持っているかどうか確認 */
 		if (uid == 16) {
				svcerr_systemerr(transp);
 
	 		return;
			}
			/*
			 * ユーザー数を求めて変数 nusers に設定するコード
			 */
			if (!svc_sendreply( transp, xdr_u_int, &nusers))
				fprintf(stderr, "can't reply to RPC call\n");
			return;
		default:
 		svcerr_noproc(transp);
			return;
	}
} 

次の点に注意してください。

最後の点で重要なのは、RPC の認証パッケージとサービスの関係です。RPC は認証を処理しますが、個々のサービスへのアクセス制御は行いません。サービス自体でアクセス制御の方針を決め、それがプロトコル内で戻り値として反映されるようにしなければなりません。

AUTH_DES タイプの認証

AUTH_SYS タイプより厳しいセキュリティレベルが要求されるプログラムでは、AUTH_DES タイプの認証を使用します。AUTH_SYS タイプは AUTH_DES タイプに簡単に変更できます。たとえば、authsys_create_default() を使用する代わりに、プログラムから authsys_create() を呼び出し、RPC 認証ハンドルを変更して目的のユーザー ID とホスト名を設定することができます。

AUTH_DES タイプの認証を使用するには、サーバー側とクライアント側の両方のホストで、keyserv() デーモンが実行されている必要があります。また、NIS または NIS+ ネームサービスも実行されている必要があります。両方のホスト上のユーザーに対してネットワーク管理者が割り当てた公開鍵 / 秘密鍵ペアが、publickey() のデータベースに入っていなければなりません。ユーザーは keylogin() のコマンドを実行して自分の秘密鍵を暗号化しておく必要があります。通常ログインパスワードと Secure RPC パスワードが同一の場合には、これを login() で行います。

AUTH_DES タイプの認証を使用するには、クライアントが認証ハンドルを正しく設定しなければなりません。その例を次に示します。

cl->cl_auth = authdes_seccreate(servername, 60, server,
 					       (char *)NULL);
 

最初の引数は、サーバープロセスのネットワーク名か、サーバープロセスの所有者のネット名です。サーバープロセスは通常 root プロセスで、次の関数呼び出しでネット名を得ることができます。

char servername[MAXNETNAMELEN];
host2netname(servername, server, (char *)NULL);

servername は受信文字列へのポインタで、server はサーバープロセスが実行されているホスト名です。サーバープロセスがスーパーユーザー以外のユーザーから起動されている場合は、次のように user2netname() を呼び出します。

char servername[MAXNETNAMELEN];
user2netname(servername, serveruid(), (char *)NULL);

serveruid() はサーバープロセスのユーザー ID です。どちらの関数も最後の引数は、サーバーを含むドメイン名です。NULL を指定すると、ローカルドメイン名が使用されます。

authdes_seccreate() の第 2 引数では、このクライアントの資格の存在時間 (ウィンドウとも呼ばれる) を指定します。この例では 60 秒が指定されているので、この資格はクライアント側が RPC 呼び出しを行なってから、60 秒間で失われます。プログラムから再びこの資格を使用しようとしても、サーバー側の RPC サブシステムは、資格がすでに失われていることを知って、資格を失ったクライアントからの要求に答えません。また資格の存在時間中に別のプログラムがその資格を再使用しようとしても拒否されます。サーバー側の RPC サブシステムが最近作成された資格を保存していて、重複して使用できないようにするためです。

authdes_seccreate() の第 3 引数は、クロックを同期させる timehost 名です。AUTH_DES タイプの認証を使用するには、サーバーとクライアントの時間が一致していなければなりません。例 5–8 では、サーバーに同期させています。(char *)NULL と指定すると同期しません。この指定は、クライアントとサーバーがすでに同期していることが確実な場合にだけ行なってください。

authdes_seccreate() の第 4 引数は、タイムスタンプとデータとを暗号化するための DES 暗号化キーへのポインタです。例 5–8 のように、 (char *)NULLchar *)NULL と指定した場合は、ランダムキーが選択されます。このキーは、認証ハンドルの ah_key フィールドに入っています。

サーバー側はクライアント側より簡単です。次に、例 5–8 のサーバーを AUTH_DES タイプの認証を使用するように変更したものを示します。


例 5–9 AUTH_DES タイプの認証を使用するサーバー

#include <rpc/rpc.h>
	...
 ...
nuser(rqstp, transp)
	struct svc_req *rqstp;
 SVCXPRT *transp;
{
	struct authdes_cred *des_cred;
 uid_t uid;
	gid_t gid;
	int gidlen;
 gid_t gidlist[10];
 
	/* NULLPROC の場合は認証データなし */
 if (rqstp->rq_proc == NULLPROC) {
		/* 元のプログラムと同じ */
 }
	/* ここで uid を取得 */
	switch(rqstp->rq_cred.oa_flavor) {
		case AUTH_DES:
			des_cred = (struct authdes_cred *) rqstp->rq_clntcred;
			if (! netname2user( des_cred->adc_fullname.name, &uid,
			                    &gid, &gidlen, gidlist)) {
				fprintf(stderr, "unknown user: %s\n",
 			         des_cred->adc_fullname.name);
				svcerr_systemerr(transp);
 			return;
			}
			break;
		default:
 		svcerr_weakauth(transp);
			return;
	}
 /* 以降は元のプログラムと同じ */

netname2user() ルーチンは、ネットワーク名 (またはユーザーの netname) をローカルシステム ID に変換することに注意してください。このルーチンはグループ ID も返します (この例では使用していません)。

AUTH_KERB 認証形式

SunOS 5.x は、klogin 以外の Kerberos V5 の大部分のクライアント側機能をサポートします。AUTH_KERBAUTH_DES と概念的に同じです。主な違いは、DES がネットワーク名と暗号化された DES セッションキーを引き渡すのに対し、Kerberos は、暗号化されたサービスチケットを引き渡すことです。実装状態と相互運用性に影響を及ぼすその他の要因については、このあとで説明します。

Kerberos はその資格が有効である時間ウィンドウの概念を使用します。クライアントまたはサーバーのクロックを制限しません。具体的には、windowauthkerb_seccreate() に引数として渡します。この場合、ウィンドウは変わりません。timehostauthkerb_seccreate() の引数として指定されると、クライアント側は timehost から時刻を取得して、時刻の差異によってタイムスタンプを変更します。時刻を同期化するには、さまざまな方法が使用できます。詳細は、 kerberos_rpc のマニュアルページを参照してください。

Kerberos ユーザーは、一次名、インスタンス、領域によって識別されます。RPC 認証コードは、領域とインスタンスを無視しますが、Kerberos ライブラリコードは無視しません。ユーザー名は、クライアントとサーバー間で同じであると仮定します。これによって、サーバーは一次名をユーザー ID 情報に変換することができます。周知の名前として 2 つの書式が使用されます (領域は省略されます)。

Kerberos は、完全資格名 (チケットとウィンドウを含むもの) の送信時に暗号文ブロックチェイン (CBC: Cipher Block Chaining) モード、それ以外の場合は、電子コードブック (ECB: Electronic Code Book) モードを使用します。CBC と ECB は、DES 暗号化のための 2 つの方法です。セッションキーは、CBC モードに対する初期入力ベクトルとして使用されます。次の表記では、XDR が objecttype とみなして使用されることを示しています。

xdr_type(object)

次のコードセクションの長さ (資格またはベリファイアのバイト数) を、4 バイト単位に丸めたサイズで表されます。完全資格名およびベリファイアは、次のようになります。

xdr_long(timestamp.seconds)
xdr_long(timestamp.useconds)
xdr_long(window)
xdr_long(window - 1)

セッションキーに等しい入力ベクトルを持つ CBC で暗号化を行うと、出力結果は次のような 2 つの DES 暗号化ブロックになります。

CB0
CB1.low
CB1.high

資格は、次のようになります。

xdr_long(AUTH_KERB)
xdr_long(length)
xdr_enum(AKN_FULLNAME)
xdr_bytes(ticket)
xdr_opaque(CB1.high) 

ベリファイアは、次のようになります。

xdr_long(AUTH_KERB)
xdr_long(length)
xdr_opaque(CB0)
xdr_opaque(CB1.low) 

ニックネーム交換によって、次のように生成されます。

xdr_long(timestamp.seconds)
xdr_long(timestamp.useconds) 

ニックネームは、ECB によって暗号化され、ECB0 と資格を得ます。

xdr_long(AUTH_KERB)
xdr_long(length)
xdr_enum(AKN_NICKNAME)
xdr_opaque(akc_nickname)
 

ベリファイアは、次のようになります。

xdr_long(AUTH_KERB)
xdr_long(length)
xdr_opaque(ECB0)
xdr_opaque(0)