Inhalt

7. Das RPC Protokoll

7.1 Die RPC-Messages

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;
        };

7.2 Das Protokoll der Sicherheitsmechanismen

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>;
        };

Sicherheitsstufe eins und zwei

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).

Das Protokoll der dritten Sicherheitsstufe (Diffie-Hellman/DES)


        /*
        * 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)    .


Inhalt