Die Kommunikation zwischen Client und Server geschieht bei RPC durch message-, bzw. PDU- (Protokoll Data Unit) -Übergabe. Der Client verschickt die call-message und der Server die reply-message. Diese Messages enthalten im XDR-Format kodierte Daten. Der Austausch von PDUs zwischen den Instanzen (Client, Server) erfolgt über fest definierte Regeln. Die Summe dieser Regel wird als Protokoll bezeichnet.
Bei der Implementierung des RPC/XDR Protokolls werden Dienste niedriger Kommunikationsschichten verwendet. Im RPC-Standard sind keine Angaben zu dem Transport-Protokoll enthalten. Sie sind der Wahl derer überlassen, die RPC auf einer konkreten Rechnerarchitektur implementieren. In UNIX-Systemen sind das meistens TCP,UDP/IP mit ihren Zugriffsschnittstellen TLI oder Sockets. So ist XDR nach dem ISO/OSI Modell in der sechsten, Darstellungsschicht und RPC in der siebten, der Applikationsschicht, anzusiedeln.
Der folgender Teil dieses Kapitels ist ein kommentierter Ausschnitt aus RFC1050.
enum msg_type {
CALL = 0,
REPLY = 1
};
enum reply_stat {
MSG_ACCEPTED = 0,
MSG_DENIED = 1
};
/*
* Erfolg und Misserfolg beziehen sich hier nicht
* auf den Ausgang der aufgerufenen Prozedur, sondern
* ausschließlich auf die RPC-Kommunikation.
* Entsprechend bedeutet der Erfolg nicht, daß die
* entfernte Prozedur überhaupt richtig aufgerufen wurde.
*/
enum accept_stat {
SUCCESS = 0,
PROG_UANVAIL = 1, /* remote hasn't exported program */
PROG_MISMATCH = 2, /* remote can't support version # */
PROC_UANVAIL = 3, /* program can't support procedure */
GARBAGE_ARGS = 4 /* procedure can't decode params */
};
enum reject_stat {
RPC_MISMATCH = 0, /* wrong RPC version # */
AUTH_ERROR = 1
};
enum auth_stat {
AUTH_BADCRED = 1, /* bad credential */
AUTH_REJECTEDCRED = 2, /* client must begin new session */
AUTH_BADVERF = 3, /* bad verifer */
AUTH_REJECTEDVERF = 4, /* verifier expired or replayed */
AUTH_TOOWEAK = 5, /* rejected for security reasons */
};
struct rpc_msg {
/*
** xid der Reply-Message gleicht xid von Call-Message.
** xid dient der Identifiakation der richtigen Antwort
** und der Call-Wiederholung, keine laufende Sequenznummer
** wird gesichert.
*/
unsigned int xid;
union switch ( msg_type) {
case CALL:
call_body cbody;
case REPLY:
reply_body rbody;
} body;
};
struct call_body {
unsigned int rpcvers; /* erlaubt verschiedene RPC-Versionen */
unsigned int prog; /* prog, vers, proc identifizieren */
unsigned int vers; /* eindeutig die im Server aufzurufende */
unsigned int proc; /* Prozedur */
opaque_auth cred;
opaque_auth verf;
/* procedure specific parameters start here */
};
union reply_body switch (reply_stat stat) {
case MSG_ACCEPTED:
accepted_reply areply;
case MSG_DENIED:
rejected_reply areply;
} reply;
struct accepted_reply {
opaque_auth verf;
union switch (accept_stat stat) {
case SUCCESS:
opaque results[0]; /* procedure result data */
case PROG_MISMATCH:
struct {
unsigned int low; /* lowest prog_version supported */
unsigned int high; /* highest prog_version supported */
} mismatch_info;
default:
void; /* include PROG_UNAVAIL, PROC_UNAVAIL, GARBAGE_ARGS */
} reply_data;
};
union rejected_reply switch (reject_stat stat) {
case RPC_MISMATCH:
struct {
unsigned int low; /* lowest RPC-version supported */
unsigned int high; /* highest RPC-version supported */
} mismatch_info;
accepted_reply areply;
case AUTH_ERROR:
auth_stat stat;
};
Auf der Protokoll-Ebene besteht die Schnittsstelle zu den Sicherheitsmechanismen aus zwei Feldern in der Service-Request Message: einen für die Identifikation (client credential) und einen für die Authentizitätsprüfung (Authentication) (client verifier). Reply-Message enthält nur ein Feld (server verifier), eine Identifikation ist hier nicht mehr nötig.
struct call_body {
.
.
opaque_auth cred; /* Identification */
opaque_auth verf; /* Verification */
};
mit:
enum auth_flavor {
AUTH_NULL = 0;
AUTH_UNIX = 1;
AUTH_SHORT = 2;
AUTH_DES = 3;
};
struct opaque_auth {
auth_flavor flavor;
opaque body<400>;
};
In der ersten Sicherheitsstufe werden Client-credential (cred) und Client- und Server-verifier (verf) zu flavor = AUTH_NULL gesetzt.
In der zweiten Sicherheitsstufe (UNIX-Identifikation) wird flavor = AUTH_UNIX gesetzt. opaque body weist folgende Struktur auf:
struct auth_unix {
unsigned int stamp;
string machinename<255>;
unsigned int uid,
gid,
gids<10>;
};
Der Server-verifier (verf) wird auf AUTH_NULL gesetzt (keine Verifizierung
der Authentizität).
/*
* Der Client kann aus Effizienzgründen außer in der ersten
* Message einen Spitznamen (Nickname) des Servers benutzen.
* Er wird vom Server vorgeschlagen.
*/
enum authdes_namekind {
ADN_FULLNAME = 0;
ADN_NICKNAME = 0;
};
typedef opaque des_block[8];
const MAXNETNAMELEN = 255;
/*
* Konversationsschlüssel ist verschlüsselt mit DES
* Zeitfenster ist die Zeitspanne für die Gültigkeit
* der folgenden Messages
*/
struct authdes_fullname {
string name<MAXNETNAMELEN>; /* Name des Clients */
des_block key; /* Konversationsschlüssel */
unsigned int window; /* Zeitfenster */
};
/*
* Nickname ist meistens der Index in der Tabelle
* des Clients, die der Server führt.
*/
union authdes_cred switch (authdes_namekind adc_namekind) {
case ADN_FULLNAME:
authdes_fullname adc_fullname;
case ADN_NICKNAME:
unsigned int adc_nickname;
};
struct timestamp {
unsigned int seconds; /* seit 1.1.1970 */
unsigned int useconds; /* Mikrosekunden */
};
/*
* Die Komponenten sind verschlüsselt mit DES, mode ECB
*/
struct authdes_verf_clnt {
timestamp adv_timestamp;
unsigned int adv_winverf; /* window verifier */
};
/*
* Bei der ersten Transaktion wird stattdessen
* folgende Struktur gebaut und als eine Einheit
* mit DES, mode CBC verschlüsselt:
*/
struct authdes_verf_clnt_first {
timestamp adv_timestamp; /* 1 DES Block */
unsigned int w = adc_fullname.window; /* 1/2 DES Block */
unsigned int adv_winverf; /* 1/2 DES Block */
};
/*
* Die Komponenten sind verschlüsselt mit DES, mode ECB
*/
struct authdes_verf_svr {
timestamp adv_timeverf;
unsigned int adv_nickname;
};
Vor der ersten Transaktion wählt der Client den Konversationsschlüssel key (mit Zufallszahlgenerator), berechnet den gemeinsamen Schlüssel ckey und verschlüsselt mit ihm den key, dh. berechnet ckey(key). Die Verschlüsselung von x mit dem Schlüssel k wird hier mit k(x) bezeichnet, win ist das Zeitfenster, t die Zeiten. Den Ablauf der Kommunikation zeigt das folgende Diagramm:
Client Server
Credential: ckey(key), key(win) ---->
Verifier: key(t1), key(win+1) ---->
<---- Verifier: key(t1-1), nickname
Credential: nickname ---->
Verifier: key(t2) ---->
<---- Verifier: key(t2-1), nickname
.
.
.
.
Bild 14
Die Methode, nach der man als einziger Netzteilnehmer aus dem gewählten Konversationsschlüssel key und dem öffentlichen Schlüssel des Servers den gemeinsamen Schlüssel ckey berechnen kann, wurde von Diffie & Hellman angegeben. Sie beruht auf folgender überlegung:
Die Kommunikationspartner wählen ihre privaten Schlüssel A und B. Für die Konstanten BASE und MODULUS berechnen sie die öffentlichen Schlüssel PA und PB nach der folgenden Regeln:
PA = (BASE ** A) mod MODULUS
PB = (BASE ** B) mod MODULUS
Doppelter Stern bedeutet, wie üblich die Potenzierung. BASE und MUDULUS
sind:
const BASE = 3;
const MODULUS = "d4a0ba0250b6fd2ec626e7efd637df76c716e22d094"
gesetzt. Für spezielle Einstellung in Ihrem system siehe
<rpc/key_prot.h>.
Nur die beiden genannten Partner im ganzen Netzwerk sind in der Lage, anschließend den gemeinsamen Schlüssel C zu finden, der nach:
C := (PB ** A) mod MODULUS
oder
C := (PA ** B) mod MODULUS
berechnet wird. Diese Tatsache beruht auf der Gleichung:
(PB ** A) mod MODULUS = (PA ** B) mod MODULUS
die bei Vernachlässigung der Modulo-Operation sofort einsichtig wird:
PB ** A = PA ** B
(BASE ** B) ** A = (BASE ** A) ** B
BASE ** (B * A) = BASE ** (A * B) .