in English in Deutsch

E - eine Sprache für (kryptographisch) sicheres, verteiltes Programmieren

von Michael Neumann <mailto:neumann@s-direktnet.de>
letzte Änderung: 22.06.2000


Mit E lassen sich sehr einfach verteilte (Client/Server) Anwendungen programmieren, die darüber hinaus selbstständig, d.h. ohne eigenes Zutun, ihre Kommunikation über eine verschlüsselte Verbindung führen. Der Vorteil von E in Hinsicht auf andere verteilte Architekturen wie z.B. COM/DCOM, CORBA oder Java-RMI, ist die Einfachheit in der Anwendung und die kryptographische Sicherheit.
E ist interpretiert und, da der Interpreter in Java geschrieben ist, sehr portabel, was jedoch auf Kosten der Effizienz geht. Vorteilhaft ist aber, daß in E problemlos Java-Klassen verwendet werden können, somit stehen sehr viele Libraries zur Verfügung. Selbstverständlich ist E kostenlos zu beziehen und Open Source.

Um den Zugriff auf ein "fernes" Objekt (ein Objekt auf einem anderen Rechner) zu erlangen verwendet E sogenannte Capabilities, ein Konzept, welches gerade in sehr sicherheits-relevanten Betriebsystemen, wie z.B. EROS (Extreme Reliable Operating System = Extrem zuverlässiges Betriebsystem), KeyKOS (Vorgänger von EROS) oder Amoeba vorkommt. Eine Capability kann man sich als Schlüssel für ein eindeutiges Objekt vorstellen, welcher den Zugriff auf dieses Objekt ermöglicht. Nur mit einer für das Objekt passenden Capability kann man dessen Methoden aufrufen. Eine Capability ist kryptographisch sicher, d.h. es ist (fast) unmöglich diese durch einen Brute-Force oder anderen Angriff herauszubekommen.

Eine Capability für ein Objekt A kann nur von einem Objekt B für welches das Objekt A sichtbar ist, d.h beide Objekte müssen unter anderem im selben Programm definiert sein, erzeugt werden. Somit kann Objekt B bestimmen, wer Zugriff auf Objekt A bekommen soll, indem es entsprechend die Capability an andere Objekte "verschickt".
Wie die Capability zu anderen Objekten gelangt, wird nicht von E geregelt, sondern muß der Programmierer selbst bestimmen. In E ist eine Capability ein URI (Uniform Resource Indentifier) und ist vergleichbar mit einer URL, d.h. eine Capability ist nicht anderes als eine Zeichenkette. Hier ein Beispiel für eine Capability in E: cap://192.168.0.5:1150/0BsXMeAG14TPw_=tYTCWTN7Q9zV3/x4dgCDy3O3NpMSn_2yUFOD.
Man könnte nun diese Capability z.B. mittels Email und PGP verschicken, oder in eine Datei schreiben, die von anderen gelesen werden kann, oder wenn schon eine verschlüsselte Verbindung besteht, einfach durch einen Methodenaufruf einem Objekt übergeben.

Wenn man nun durch eine Capability auf ein "fernes" Objekt zugreift, kann man nicht einfach, wie dies bei lokalen Objekten möglich ist, dessen Methoden aufrufen, sondern man macht sogenannte "eventual sends". "Eventual sends" werden durch die Aufruf-Syntax von Methodenaufrufen unterschieden, so ist anObject method(1,2) ein Methodenaufruf (es wird die Methode method des Objektes anObject mit den Parametern 1,2 aufgerufen) und anObject <- method(1,2) ist der vergleichbare "eventual send". Das Besondere an "eventual sends" ist, daß nicht auf die Ausführung der "fernen" Methode gewartet wird, sondern sofort die nächste Anweisung im Programm verarbeitet wird. Da "Eventual sends" nicht den Wert des fernen Methodenaufrufes liefern können, da dieser noch nicht berechnet wurde, liefern sie ein sogenanntes "Promise"-Objekt (Versprechen). Dieses Objekt enthält des Status des Aufrufs, d.h. ob die ferne Methode schon fertig mit ihrer Ausführung ist und ob der zurückgegebene Wert schon auf dem lokalen Computer verfügbar ist. Wenn dies der Fall ist, kann man den Wert des Aufrufes ermitteln.
Es gibt eine spezielle Syntax

when (promiseObject) -> done(resultValue) { 
   execute_when_done 
} catch error { 
   execute_when_error
}
die den Block zwischen done und catch ausführt (execute_when_done), wenn für promiseObject ein lokaler Wert verfügbar ist, der in resultValue gespeichert wird. Es wird jedoch gleich zur nächsten Anweisung (nach dem catch-Block) verzweigt, d.h. es wird nicht auf die Ausführung des Blockes gewartet bzw. bis ein lokaler Wert vorhanden ist. Wenn ein Fehler auftritt (z.B. Verlust der Verbindung), wird der catch-Block ausgeführt.


Nun ist Praxis angesagt. Im folgenden soll eine Client/Server-Anwendung programmiert werden. Der Server soll dabei eine Berechnung durchführen und das Ergebnis zurückgeben. Als Berechnung nehmen wir einfach eine Addition zweier Zahlen. Der Client gibt dann die Ergebnisse der auf dem Server ausgeführten Berechnungen auf dem Bildschirm aus. Die Capability des Server-Objektes soll durch eine Datei dem Client zugänglich gemacht werden.

Server-Quellcode:

# zuerst muss der C/S-Mechanismus initialisiert werden
introducer onTheAir

# Objekt, welches dem Client später zugänglich gemacht wird
define ComputeService {

   # Methode "add"
   to add( x, y ) : any {
      x + y
   }

}

# Funktion, die eine Capability (URI) für das
# übergebene Objekt zurückliefert
define makeURIFromObject( obj ) : any {
    introducer sturdyToURI( sturdyRef( obj ) )
}


# Capability (URI) aus dem Objekt ComputeService erzeugen
define URI := makeURIFromObject( ComputeService )

# Datei test.cap öffnen
define file := <file:test.cap>

# Capability in die Datei schreiben
file setText( URI )

# anzeigen, daß der Server bereit ist,
# nun kann der Client gestartet werden
print( "ready" )

# das Programm in eine Endlos-Schleife schicken
interp blockAtTop


Und nun der Client-Quellcode:

# Client muß ebenfalls den C/S-Mechanismus initialisieren
introducer onTheAir

# gibt das Objekt welches durch den übergebenen URI (Capability) 
# repräsentiert wird zurück
define getObjectFromURI( uri ) : any {
    introducer sturdyFromURI( uri ) liveRef
}


# Datei test.cap auslesen
define URI := <file:test.cap> getText

# das "ferne" Objekt von dem URI erzeugen
define remoteComputeService := getObjectFromURI( URI )


for a in 0..10 {
   for b in 0..10 {

      # der Block innerhalb von when, wird ausgeführt, wenn der lokale Wert
      # vorhanden wird, jedoch wird im Programm sofort zur nächsten Anweisung 
      # übergegeangen (nächste Iteration der for-Schleife)
      when ( remoteComputeService <- add( a, b ) ) -> done( res ) {
         # "a+b=res" auf Bildschirm ausgeben
         println( "" + a + "+" + b + "=" + res )
      } catch problem {}

   }
}

# endlosschleife, da ansonsten das Programm beendet bevor 
# die Berechnungen durchgeführt wurden (da "when" nicht wartet)
interp blockAtTop
Das war's...


Referenzen