![]() |
|||||||||||||||||||||||
|
Bevor die Netzwerkfunktionalität von Java betrachtet werden kann, muss die Sicherheitsfunktionalität der Sprache Java im Detail erläutert werden. Die im Folgenden angegebene Darstellung baut auf der in Kapitel 1.3 angegebenen allgemeinen Erläuterung des Sicherheitsmodells von Java auf. Security-Manager Jede Java-Anwendung kann einen eigenen Security-Manager verwenden, der sämtliche Sicherheitsrestriktionen einer Anwendung überwacht. Hierzu steht im Package java.lang die Klasse SecurityManager zur Verfügung, die als abstrakte Klasse die Programmierschnittstelle und teilweise auch Implementierungen von Java-Security-Managern anbietet. Standardmäßig verfügt eine Anwendung nicht über einen Security-Manager. Operationen, die in Anwendungen ausgeführt werden, unterliegen daher den allgemeinen Sicherheitsrestriktionen, wenn kein spezieller Security-Manager definiert wird. Spezielle Browser und Appletviewer erzeugen allerdings beim Aufruf einen Security-Manager. Ein Applet unterliegt daher stets den Sicherheitsrestriktionen, die vom Security-Manager einer bestimmten Anwendung definiert werden, in der ein Applet ausgeführt wird. Um einen Security-Manager für eine Anwendung zu erzeugen, wird die Methode getSecurityManager() der Klasse System verwendet:
SecurityManager sm = System.getSecurityManager(); Der Aufruf dieser Methode liefert den Wert null zurück, wenn kein geeigneter Security-Manager zur Verfügung steht, der initialisiert werden könnte. Es sollte daher stets geprüft werden, ob eine Instanz zur Verfügung steht, bevor die entsprechenden Methoden aufgerufen werden. Nachdem ein Security-Manager erzeugt wurde, muss die Erlaubnis dafür angefordert werden, bestimmte Operationen zu ermöglichen oder zu verbieten. Dieses Vorgehen wird von vielen Klassen der Java-Packages angewendet, bspw. von der Methode System.exit(), die den Java-Interpreter beendet, indem die Methode checkExit() des Security-Managers verwendet wird, um die Anwendbarkeit dieser Operation zu prüfen. Das folgende Beispiel illustriert dieses Vorgehen.
SecurityManager s = System.getSecurityManager(); if (s != null) { s.checkExit(status); } Wenn ein Security-Manager die exit-Operation genehmigt, wird die checkExit()-Methode regulär abgeschlossen, anderenfalls erzeugt der Aufruf der Methode checkExit() eine SecurityException-Ausnahme. Die Klasse SecurityManager definiert eine Reihe weiterer Methoden, die zur Verifikation von Operationen eingesetzt werden können. Beispiele hierfür sind
Jede Operation oder Operationsgruppe verfügt über spezielle Implementierung eines Security-Managers Ein Security-Manager wird implementiert, indem eine Subklasse der Klasse SecurityManager erzeugt wird, die einige der Methoden der Klasse SecurityManager überschreibt, um Sicherheitsrestriktionen zu modifizieren. Im Folgenden wird anhand eines Beispiels betrachtet, wie Zugriffe auf das Dateisystem eingeschränkt werden können. Hierbei wird jeweils die checkRead()-Methode des Security-Managers aufgerufen, wenn eine Datei zum Lesen geöffnet werden soll. In ähnlicher Art und Weise wird die Methode checkWrite() dazu verwendet, die Berechtigung zum Schreiben von Daten zu prüfen. Wenn der Security-Manager eine derartige Operation nicht zulässt, wird eine Security-Exception (Sicherheitsverletzung) ausgelöst. Um diese Funktionalität zu implementieren, muss die Subklasse der Klasse SecurityManager die Methoden checkRead() und checkWrite() überschreiben. Die Klasse SecurityManager beinhaltet drei Arten von checkRead()-Methoden und zwei Arten von checkWrite()-Methoden. Jede dieser Methoden sollte verifizieren, ob eine Anwendung eine Datei zum Lesen oder Schreiben öffnen darf. Hierbei ist zu bedenken, dass Applets, die über das Netz geladen werden, Zugriffe auf das lokale Dateisystem meist nicht ausführen dürfen, falls der Security-Manager nicht modifiziert wird. Im folgenden Beispiel wird der Benutzer nach einem Passwort gefragt, wenn er auf das lokale Dateisystem zugreifen will. Der Zugriff wird gestattet, wenn das Passwort korrekt eingegeben wird.
class PasswortSecurityManager extends SecurityManager { //Operationen } Anschließend wird innerhalb der Klasse PasswortSecurityManager eine als private gekennzeichnete Instanzvariable passwort deklariert, die das Passwort enthält, das der Benutzer eingeben muss, um Zugriff auf die Systemressourcen zu erhalten.
PasswortSecurityManager(String passwort) { super(); } Die anschließend deklarierte Methode accessOK() fungiert als Hilfsfunktion. Hier wird der Benutzer aufgefordert, ein Passwort einzugeben, das in der Folge überprüft wird. Die Methode liefert den Wert true zurück, wenn das Passwort korrekt ist, anderenfalls den Wert false. Zur Wiederholung der Konzepte, die im ersten Unterkapitel (Streaming) erläutert wurden, wird hier ein Dateneingabestrom verwendet.
private boolean accessOK() { int c; antwort = dis.readLine(); return true; else return false; } catch (IOException e) { return false; } } Den Abschluss der Implementierung bildet das Überschreiben der drei checkRead()-Methoden bzw. der zwei checkWrite()-Methoden.
public void checkRead(FileDescriptor filedescriptor) { if (!accessOK()) throw new SecurityException("Zugriff verweigert"); } if (!accessOK()) throw new SecurityException("Zugriff verweigert"); } if (!accessOK()) throw new SecurityException("Zugriff verweigert"); } if (!accessOK()) throw new SecurityException("Zugriff verweigert"); } if (!accessOK()) throw new SecurityException("Zugriff verweigert"); } } Alle checkXXX()-Methoden rufen die Methode accessOK() auf, damit der Benutzer das Passwort eingeben kann. Wird der Zugriff verweigert, so wird eine Security-Exception erzeugt. SecurityException-Objekte sind Laufzeit-Exceptions und müssen daher nicht mittels throws als Teil der Methoden deklariert werden. checkRead() und checkWrite() sind Beispiele dafür, wie checkXXX()-Methoden überschrieben werden können. Es ist zu beachten, dass nicht alle checkXXX()-Methoden eines Security-Managers überschrieben werden müssen, sondern stets nur diejenigen, deren Funktionsweise modifiziert werden soll. Die Standardimplementierung der checkXXX()-Methoden des Security-Managers erzeugt im Fehlerfall allerdings immer eine Security-Exception, verbietet also alle Operationen, die Sicherheitseinschränkungen unterliegen. Alle checkXXX()-Methoden des Security-Managers operieren auf dieselbe Art und Weise:
Installation des Security-Managers Nachdem eine Subklasse der Klasse Im folgenden Beispiel wird die Klasse PasswortSecurityManager als Security-Manager installiert. Um zu prüfen, ob der Security-Manager wirklich arbeitet, werden zwei Dateien geöffnet. Die erste Datei wird hierbei ausgelesen und in die zweite kopiert. Zunächst wird ein Eingabestrom angelegt, damit der Benutzer sein Passwort eingeben kann. Dieser Strom wird als Argument an den Security-Manager übergeben.
import java.io.*; public static void main(String[] args) throws Exception { BufferedReader buf = new BufferedReader(new InputStreamReader(System.in)); System.setSecurityManager(new PasswortSecurityManager("Open Java", buf)); } catch (SecurityException e) { System.out.println("SecurityManager bereits gesetzt"); } Zuerst wird ein Security-Manager angelegt, der als Argument das Passwort Open Java erhält. Diese Instanz wird an die Methode setSecurityManager() übergeben. Der derart installierte Security-Manager ist so lange aktiv, wie die Anwendung läuft. Hierbei ist zu beachten, dass ein Security-Manager in einer Anwendung nur genau einmal gesetzt werden darf. Weitere Versuche resultieren in einer Security-Exception. Die restlichen Zeilen des Programms kopieren die Datei beispiel1.txt in die Datei beispiel2.txt. Diese Funktion dient als Beweis dafür, dass der Security-Manager wie gewünscht arbeitet.
BufferedReader in = new BufferedReader(new FileReader("beispiel1.txt")); out.println(is); in.close(); } } Es ist zu beachten, dass im Beispiel das Passwort zweimal angefordert wird, da auf zwei Dateien zugegriffen werden muss. Das Beispiel arbeitet nur dann korrekt, wenn das Passwort in beiden Fällen korrekt eingegeben wird. Anderenfalls stürzt die Anwendung ab, da die Exception nicht abgefangen wird. Überschreiben von Methoden des Security-Managers In Abhängigkeit von den Operationen, die der Security-Manager überwachen soll, müssen einige der checkXXX()-Methoden überschrieben werden. In Tab. 5-6 ist dargestellt, welche Operationen auf welchen Objekten eingesetzt werden können, um Sicherheitsrestriktionen umzusetzen. |
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Tab. 5.6: Objekte und Sicherheitsoperationen In Abhängigkeit von der angestrebten Sicherheitsfunktionalität können lediglich einige, aber auch alle dieser Methoden überschrieben werden. Die Standardimplementierung, die in der Klasse SecurityManager zur Verfügung steht, sieht wie folgt aus:
public void checkXXX(. . .) { throw new SecurityException(); } Kontrolle von Applets und Applications Im Folgenden werden die Möglichkeiten erläutert, die in Java zur Kontrolle von Applets und Applications zur Verfügung stehen. Applets In Java werden Security-Manager dazu eingesetzt, Sicherheitsrisiken so weit wie möglich zu minimieren. Die meisten Browser installieren einen Security-Manager, mit dem die Ausführung von Applets kontrolliert werden kann. Jedes Applet kann in der Folge Systemressourcen nur dann verwenden, wenn der Security-Manager dies explizit gestattet. Java-Umgebungen, die mit JDK 1.2 kompatibel sind, müssen derartige Vorgänge in einer sog. Policy-Datei vermerken. Das folgende Applet versucht, Daten in eine Datei des derzeit verwendeten Verzeichnisses zu schreiben. Dies ist solange unmöglich, wie kein Eintrag in einer Policy-Datei vorgenommen wird.
import java.awt.*; String datei = "beispiel"; try { dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(datei),100)); }catch (SecurityException e) { g.drawString("Security Exception: " + e, 10, 10); } g.drawString("I/O Exception", 10, 10); } } } Wird das Applet in dieser Form aufgerufen, so wird eine Security-Exception erzeugt, da auf Ressourcen zugegriffen wird, auf die Applets für gewöhnlich nicht zugreifen dürfen. Eine Policy-Datei liegt stets in Form von ASCII-Text vor und kann mit einem Texteditor oder mit dem im Folgenden beschriebenen Policy-Tool bearbeitet werden. Mit Hilfe des Policy-Tools ist die Bearbeitung wesentlich komfortabler, da die erforderliche Syntax nicht beachtet werden muss. Zur Verwendung des Policy-Tools müssen die folgenden Schritte durchlaufen werden:
Abb. 5.6: Policy-Tool Diese Schritte sind für Benutzer verschiedener Betriebssysteme identisch. Es muss allerdings beachtet werden, dass sich die im Folgenden verwendeten Pfadnamen auf Windows-Betriebssysteme beziehen. Um das Policy-Tool aufzurufen, muss lediglich die Anweisung policytool in der Kommandozeile eingegeben werden, woraufhin das Fenster des Policy-Tools erscheint. Jedes Mal, wenn das Policy-Tool aufgerufen wird, werden bereits einige Informationen im Fenster eingetragen, bspw. der Dateiname der Policy-Datei, die standardmäßig als .java.policy im Home-Directory des Benutzers angelegt wird. Kann das Policy-Tool diese Datei nicht finden, so erfolgt eine Fehlermeldung und das entsprechende Feld des Fensters bleibt leer (siehe Abb. 5-6).
Abb. 5.5: Policy-Einträge In diesem Fall kann entweder eine Policy-Datei geöffnet oder eine neue Policy-Datei angelegt werden. Wird das Policy-Tool zum ersten Mal verwendet, so steht noch keine derartige Datei zur Verfügung und das Fenster ist stets leer. In diesem Fall ist zunächst eine Policy-Datei anzulegen, wie im Folgenden beschrieben wird. Um dem Beispiel-Applet die erforderlichen Berechtigungen einzuräumen, muss in der Policy-Datei ein entsprechender Eintrag angelegt werden. Hierzu ist zunächst die Option Add Policy Entry im Policy-Tool auszuwählen, wodurch ein neues Fenster erscheint (siehe Abb. 5-5). Ein Policy-Eintrag gibt eine oder mehrere Berechtigungen für Code-Segmente an, die entweder in Form einer URL, in Form signierten Codes oder in beiden Formen vorliegen können. Die Textbereiche CodeBase und SignedBy werden dazu benutzt, anzugeben, welcher Code Berechtigungen erhalten soll. Der Parameter CodeBase zeigt hierbei an, wo der Code gespeichert ist, der die Berechtigung erhält. Wird dieses Feld leer gelassen, so bezieht sich die Berechtigung auf jedes Programm, das ausführbar ist.
Abb. 5.7: Berechtigungen Der Wert des Feldes SignedBy bezeichnet einen Alias-Namen für ein Zertifikat, das in einem Keystore gespeichert ist. Der öffentliche Schlüssel dieses Zertifikats wird dazu eingesetzt, um die digitale Signatur des Codes zu verifizieren. Die Berechtigung wird daher einem Code-Segment eingeräumt, das mit dem privaten Schlüssel unterschrieben ist, zu dem der öffentliche Schlüssel gehört, der im Keystore gespeichert ist und auf den sich der Alias-Name bezieht. Das SignedBy-Feld ist allerdings optional. Wird dieses Feld nicht ausgefüllt, so spielt es keine Rolle, ob der Code signiert ist oder nicht bzw., wer den Code signiert hat. Wurden beide Felder ausgefüllt, so beziehen sich die Berechtigungen nur auf den Code, der an der angegebenen Stelle gespeichert ist und der vom angegebenen Alias-Namen unterschrieben ist. Nachdem CodeBase und eventuell SignedBy angegeben wurden, können die Berechtigungen angegeben werden. Hierzu muss der Button Add Permission betätigt werden, woraufhin das Fenster Permissions erscheint (siehe Abb. 5-7). Um die Berechtigungen geeignet zu setzen, sind die folgenden Schritte auszuführen:
Nachdem die Berechtigungen gesetzt wurden, muss der Nachdem die Bearbeitung der Policy-Datei abgeschlossen ist, muss diese noch gespeichert werden. Hierzu wird aus dem Menü File die Option Save As ausgewählt, woraufhin der Name angegeben werden kann, unter dem die Policy-Datei gespeichert werden soll. Im Anschluss daran kann das Policy-Tool beendet werden, indem im Menü File die Option Exit ausgewählt wird. Nachdem eine Policy-Datei erzeugt wurde, muss sie geladen werden. Wenn ein Applet bzw. eine Application mit einem Security-Manager abläuft, so werden standardmäßig die Policy-Dateien geladen, die in der sog. Security-Properties-Datei gespeichert sind. Bezeichnet man mit java.home das Verzeichnis, in dem Java installiert ist, so ist die Security-Properties-Datei in den folgenden Verzeichnissen zu finden:
Die Speicherorte der Policy-Dateien werden in der Security-Properties-Datei stets in der Form Üblicherweise bezeichnet man mit der Syntax ${variablenName} Um die im vorangegangenen Schritt erstellte Policy-Datei aufzurufen, sind zwei Vorgehensweisen denkbar:
Um die erste Möglichkeit zu verwenden, muss in der Kommandozeile des Appletviewers der Name der Policy-Datei angegeben werden, die zusätzlich verwendet werden soll. Hierbei kann auch angegeben werden, dass die Security-Properties-Datei nicht verwendet werden soll.
appletviewer -J-Djava.security.policy=eigenepolicy DateiSchreiben.html Ergeben sich in der Ausführung des Applets Fehler, so muss die Policy-Datei bspw. mit Hilfe des Policy-Tools auf Fehler untersucht werden. Die andere Möglichkeit besteht darin, der Security-Properties-Datei mit Hilfe eines Text-Editors eine weitere Zeile hinzuzufügen. Hierzu ist die URL anzugeben, unter der die Policy-Datei zu finden ist, bspw. in der folgenden Form (Windows-Systeme):
policy.url.3=file:/C:/eigenepolicy Nachdem die Security-Properties-Datei gespeichert wurde, kann das Applet bspw. mit Hilfe des Appletviewers ohne Angabe zusätzlicher Parameter ausgeführt werden. Ist dies nicht der Fall, so sollten Fehler in der Policy-Datei gesucht werden. Applications Wird eine Application ausgeführt, so wird nicht automatisch ein Security-Manager installiert. Um aber dieselben Sicherheitseinschränkungen verwenden zu können wie bei der Ausführung von Applets, kann der Interpreter mit der folgenden Option aufgerufen werden:
java -Djava.security.manager dateiname Hierbei wird standardmäßig die System-Policy-Datei geladen, die allen Code-Segmenten die Möglichkeit einräumt, auf allgemeine Eigenschaften zuzugreifen. Zusätzliche Eigenschaften werden allerdings nicht zur Verfügung gestellt, Zugriffe hierauf resultieren daher in Fehlern. Die System-Policy-Datei ist standardmäßig in folgendem Verzeichnis abgelegt:
Um den Zugriff so einzurichten, wie es für eine spezielle Application erforderlich ist, muss erneut eine Policy-Datei modifiziert werden. Dies erfolgt in der Art und Weise, wie Policy-Dateien für Applets angelegt werden. Zur Verwendung der Policy-Dateien stehen wiederum zwei Möglichkeiten zur Verfügung: Der Aufruf über die Kommandozeile oder der Eintrag in der Security-Properties-Datei. Zum Aufruf in der Kommandozeile ist die folgende Syntax zu verwenden: Die Modifikation der Security-Properties-Datei erfolgt in analoger Art und Weise wie bei Applets. Nachdem diese Datei geeignet verändert wurde, sind die Sicherheitseinschränkungen dann wirksam, wenn das Programm wie folgt aufgerufen wird:
java -Djava.security.manager dateiname APIs und Security-Tools Im Folgenden werden die erweiterten Sicherheitsfunktionen von Java betrachtet. Um das Verständnis der folgenden Unterkapitel zu ermöglichen, werden zunächst wichtige Begriffe der Sicherheitstechnik eingeführt bzw. wiederholt. Werkzeuge (Tools), die hierbei zum Einsatz kommen, sind bspw. der Vorgang des Signierens von Code bzw. das Einräumen von Berechtigungen sowie der Dateiaustausch. APIs kommen zum Einsatz, um Signaturen zu erzeugen und um diese zu verifizieren. Wenn ein Dokument elektronisch übertragen wird, so muss sichergestellt werden, dass das Dokument bzw. der Code auch tatsächlich vom Absender stammt, bzw. dass die Daten während der Übertragung nicht modifiziert wurden. Diese Funktion ist die Aufgabe digitaler Signaturen. Digitale Signaturen
Zertifikate Üblicherweise muss der Empfänger auch sicherstellen, dass der öffentliche Schlüssel authentisch ist. Zur Realisierung dieser Funktionalität werden Zertifikate eingesetzt. Ein Zertifikat enthält stets
Eine Möglichkeit, mit der der Empfänger überprüfen kann, ob ein Zertifikat gültig ist, besteht darin, die digitale Signatur des Unterzeichnenden mit Hilfe seines öffentlichen Schlüssels auszuwerten. Dieser Schlüssel kann wiederum von einer weiteren Institution zertifiziert sein, bis ein bestimmter öffentlicher Schlüssel zur Dekodierung verwendet wird, dem in jedem Fall vertraut werden kann. Kann der Empfänger eine derartige Kette nicht aufbauen, da bspw. keine derartigen Instanzen zur Verfügung stehen, so kann der Fingerabdruck des Zertifikats berechnet werden. Hierzu kann das Werkzeug keytool (Optionen -import oder -printcert) eingesetzt werden. Jeder Fingerabdruck besteht aus einer kurzen Zahl, die das Zertifikat eindeutig und zuverlässig identifiziert (Hash-Code). Der Empfänger kann dann den Owner des Zertifikats kontaktieren und die Fingerabdrücke der gesendeten und der empfangenen Daten vergleichen. Sind diese identisch, so sind auch die Zertifikate gleich. Auf diese Art und Weise kann sichergestellt werden, dass ein Zertifikat während der Übertragung nicht modifiziert wurde. Eine weitere potentielle Unsicherheit besteht aber in der Identität des Absenders. Manchmal kann ein Zertifikat auch selbst unterschrieben sein, indem die Unterschrift mit dem privaten Schlüssel geleistet wird, dessen korrespondierender öffentlicher Schlüssel im Zertifikat enthalten ist. Hierdurch kann bewiesen werden, dass der Inhalt des Zertifikats zu einem privaten Schlüssel gehört. Dieser Fall ist nur dann vertrauenswürdig, wenn der Empfänger den Absender bereits kennt und diesem vertraut. Anderenfalls muss der Sender ein Zertifikat von einer vertrauenswürdigen dritten Partei erhalten, die auch als Certification Authority (CA) bezeichnet wird. Um dies zu erreichen, sendet man eine selbst signierte Nachricht, einen sog. Certificate Signing Request (CSR) an eine CA. Die CA verifiziert die Signatur, die in der CSR enthalten ist, und die Identität des Absenders. Anschließend bürgt die CA dafür, dass der Sender der Eigentümer eines öffentlichen Schlüssels ist, indem ein Zertifikat ausgestellt wird und dieses mit dem privaten Schlüssel der CA unterschrieben wird. Jeder, der dem öffentlichen Schlüssel der CA vertraut, kann dann die Signatur des Zertifikats verifizieren. In vielen Fällen wird die CA selbst von einer weiteren CA zertifiziert. Aus diesem Grund sind CAs meist in Form von Hierarchien organisiert. Lediglich der CA auf oberster Ebene müssen alle Partner vertrauen. Zertifikate von Entitäten, denen ein Sender vertraut, werden üblicherweise in Schlüsseldatenbanken, sog. Keystores, als „trusted Zertifikate" geladen. Der öffentliche Schlüssel jedes Zertifikats kann dann dazu verwendet werden, um Signaturen zu verifizieren, die mit einem entsprechenden privaten Schlüssel erzeugt wurden. Derartige Prüfungen können folgendermaßen erfolgen:
Wenn signierter Code oder signierte Dokumente an andere gesendet werden, so muss das Zertifikat beigefügt sein, das den öffentlichen Schlüssel enthält und mit dessen korrespondierendem privaten Schlüssel das Dokument bzw. der Code unterschrieben wurde. Mit Hilfe der Option -export des Werkzeugs keytool oder mit Hilfe von API-Methoden kann ein Zertifikat aus einem Keystore in eine Datei exportiert werden, die dann übertragen werden kann. Der Empfänger kann dann dieses Zertifikat in seinen Keystore als Trusted Certificate importieren, bspw. indem API-Methoden verwendet werden oder die Option -import des Werkzeugs keytool. Wird jarsigner dazu verwendet, eine Signatur einer JAR-Datei zu erstellen, so lädt das Werkzeug das Zertifikat bzw. die entsprechende Zertifikatskette aus dem Keystore und speichert diese zusammen mit der Signatur in der JAR-Datei. Keystores Private Schlüssel und entsprechende Zertifikate mit öffentlichen Schlüsseln werden in Datenbanken, sog. Keystores gespeichert und mit einem Passwort geschützt. Ein Keystore kann stets zwei Arten von Einträgen beinhalten: Trusted Zertifikate und Einträge der Form [Schlüssel/Zertifikat]. Jeder Eintrag muss jeweils einen privaten Schlüssel und das entsprechende Zertifikat mit öffentlichem Schlüssel enthalten. Jeder Eintrag eines Keystores wird durch einen Alias-Namen identifiziert. Der Besitzer eines Keystores kann eine Vielzahl verschiedener Schlüssel in einem Keystore verwalten, auf die mit Hilfe verschiedener Alias-Namen zugegriffen wird. Ein Alias-Name wird typischerweise nach einem bestimmten Kontext benannt, in dem der Besitzer den privaten Schlüssel verwendet, bspw. nach dem Einsatzzweck des Schlüssels. Der Eintrag signiereJarFiles könnte bspw. angeben, dass der Eintrag zur Signatur von JAR-Dateien verwendet wird. Das Werkzeug keytool kann dazu verwendet werden,
Auch API-Methoden können zum Zugriff auf einen Keystore bzw. zu dessen Verwaltung verwendet werden. Tools und APIs Das Security-API des JDKs kann, ebenso wie die Werkzeuge (Tools), in Kombination oder alleine dazu verwendet werden, um Schlüssel und Signaturen zu erzeugen und um Zertifikate zu importieren. Tools und APIs können daher dazu eingesetzt werden, um Dokumente oder Code auf sichere Art und Weise mit Dritten auszutauschen. Um diesen Austausch vornehmen zu können, müssen Dokumente oder Code in JAR-Dateien gespeichert werden, die bspw. mit Hilfe des Werkzeugs jar erstellt werden können. Mittels JAR-Dateien können mehrere Dateien auf einfache Art und Weise zusammengebunden werden. Wird eine Datei signiert, so muss die resultierende digitale Signatur an anderer Stelle gespeichert werden. Im Falle einer JAR-Datei kann die Signatur allerdings direkt in der JAR-Datei gespeichert werden. Hierzu wird das Werkzeug jarsigner verwendet. An dieser Stelle sollte darauf aufmerksam gemacht werden, dass keine Unterscheidung zwischen der Unterzeichnung von Applets oder Applications gemacht wird. Beide Arten müssen in einer JAR-Datei gespeichert und signiert werden. Wenn eine Laufzeitumgebung eine Code-Signatur auswerten will, muss die Person bzw. Organisation zunächst ein Zertifikat in ihren Keystore importieren, das den öffentlichen Schlüssel authentifiziert, der zum privaten Schlüssel gehört, mit dem der Code signiert wurde. Signaturen Nachdem im vorangegangenen Abschnitt grundlegende Begriffe der sicheren Datenübertragung erläutert wurden, wird in diesem Abschnitt erklärt, wie die Security-Tools keytool, jarsigner und policytool funktionieren bzw. wie sie zusammenwirken. In diesem Kontext wird nochmals die Funktion des Werkzeugs jar betrachtet, das mit jarsigner bei der Signatur von Java-Archiven zusammenarbeitet. Zuerst wird erläutert, wie der Sender von Daten vorgehen muss, wenn er Daten signieren will. Die Techniken, die in Bezug auf den Sender in diesem Unterkapitel vermittelt werden, lassen sich in folgendes Schema gliedern:
Funktionalität des Senders Im ersten Schritt wird zunächst eine Anwendung erstellt, die zu einem späteren Zeitpunkt beim Empfänger ausgeführt werden soll. Diese Anwendung beinhaltet eine sicherheitskritische Funktion, das Lesen von der Festplatte. Die Implementierung ist sehr einfach: Ein Benutzer gibt einen Dateinamen über die Kommandozeile an. Anschließend wird zum Auslesen einer Datei ein
import java.io.*; public static void zeilenAusgabe (BufferedReader in) throws IOException { String is; System.out.println(is); } if (args.length >= 1) zeilenAusgabe(new BufferedReader(new FileReader(args[0]))); else System.err.println("Eingabe: Ausgabe Dateiname"); } } Es sollte beachtet werden, dass die Datei, die eingelesen werden soll, in einem anderen Verzeichnis gespeichert sein sollte, als in demjenigen, in dem der Java-Quellcode ausgeführt wird. Im Verlauf dieses Unterkapitels wird erkennbar werden, dass eine Anwendung, die innerhalb eines Security-Managers ausgeführt wird, Daten in solchen Verzeichnissen nicht lesen darf, die vom Verzeichnis der .class-Datei abweichen, wenn die diesbezüglichen Berechtigungen nicht gesetzt sind. Einen Anwendung darf Dateien allerdings immer aus dem Verzeichnis lesen, in dem auch die .class-Datei abgelegt ist. Das Beispiel kann daher nur dann sinnvoll getestet werden, wenn die Speicherorte der .class-Datei und der einzulesenden Datei unterschiedlich sind. Nachdem die Anwendung erstellt und übersetzt ist, muss eine JAR-Datei erzeugt werden, die die Datei Ausgabe.class enthält. Hierzu ist das folgende Kommando einzugeben:
jar cvf Ausgabe.jar Ausgabe.class Hierdurch wird eine JAR-Datei Ausgabe.jar angelegt, in der die Datei Ausgabe.class gespeichert ist. Nach dem Anlegen der JAR-Datei muss der Code signiert werden. Wenn hierzu noch kein geeigneter privater Schlüssel zur Verfügung steht, muss dieser zunächst zusammen mit dem dazugehörigen öffentlichen Schlüssel erstellt werden. Der öffentliche Schlüssel kann dann vom Empfänger dazu verwendet werden, um die Signatur zu verifizieren. Im Folgenden sei angenommen, dass Schlüssel für einen Benutzer namens beispiel, der für eine Firma namens Firma arbeitet, angelegt werden sollen. Um einen Keystore bzw. die notwendigen Schlüssel zu erzeugen, muss in der Kommandozeile die folgende Anweisung eingegeben werden:
keytool -genkey -alias signaturDatei -keypass bspkap5 -keystore beispielstore -storepass Fir123ma Es ist zu beachten, dass diese Anweisung in einer einzelnen Zeile eingegeben wird. In Tab. 5-7 ist angegeben, welche Bedeutung die einzelnen Teile der oben verwendeten Anweisung haben. |
|
|
Tab. 5.7: Erzeugung von Schlüsselpaaren Aus Sicherheitsgründen sollten die Passwörter für Schlüssel und Keystore nicht in der Kommandozeile angegeben werden, da derartige Eingaben ausgespäht werden könnten. Anstelle dessen sollten die Optionen keypass und storepass nicht angegeben werden und das jeweilige Passwort dann spezifiziert werden, wenn das System dies verlangt. Wenn das oben angegebene Kommando ausgeführt wird, werden Informationen über den sog. Distinguished Name angefordert. Hierbei werden die folgenden Daten abgefragt. Jeweils nach dem Ausdruck in eckigen Klammern ist angegeben, welche Informationen eingegeben wurden.
What is your first and last name? [Unknown]: Stephan Fischer What is the name of your organizational unit? [Unknown]: E-Technik What is the name of your organization? [Unknown]: Firma What is the name of your City or Locality? [Unknown]: Darmstadt What is the name of your State or Province? [Unknown]: Germany What is the two-letter country code for this unit? [Unknown]: DE Is <CN=Stephan Fischer, OU=E-Technik, O=Firma, L=Darmstadt, ST=Germany, C=DE> correct? [no]: y Das derart eingesetzte keytool-Kommando erzeugt einen Keystore namens beispielstore, falls dieser nicht bereits existiert. Der Keystore wird in dem Verzeichnis gespeichert, in dem das Kommando ausgeführt wurde. Für Zugriffe wird das Passwort Fir123ma vereinbart. Weiterhin wird ein Paar aus öffentlichem und privatem Schlüssel angelegt, das über einen Distinguished Name näher bezeichnet ist. Die Anweisung erzeugt weiterhin ein Zertifikat, das mit dem privaten Schlüssel unterschrieben ist (selbst signiert) und das den Distinguished Name im subject-Feld des Zertifikats enthält. Dieses Zertifikat hat eine Gültigkeit von 90 Tagen, falls nicht mittels der Option validity ein anderer Wert angegeben wird. Das Zertifikat ist dem privaten Schlüssel zugeordnet, der mit dem Alias-Namen signaturDatei angesprochen werden kann. Der private Schlüssel kann nur verwendet werden, wenn das Passwort, in diesem Fall bspkap5, bekannt ist. Nach der Generierung von Schlüsseln und Keystore stehen alle Mittel zur Verfügung, um die bereits angelegte JAR-Datei zu signieren. Hierzu muss die folgende Anweisung auf Kommandozeilenebene verwendet werden:
jarsigner -keystore beispielstore -signedjar sAusgabe.jar Ausgabe.jar signaturDatei Bei der Ausführung dieser Anweisung wird der Keystore beispielstore verwendet. Der Name der signierten Datei wird mit der Option signedjar angegeben. Der letzte Ausdruck der Anweisung entspricht dem Alias-Namen des privaten Schlüssels, mit dem signiert werden soll. Führt man die Anweisung aus, so wird zunächst das Passwort des Keystores erfragt (Fir123ma), anschließend das Passwort des privaten Schlüssels (bspkap5). jarsigner extrahiert weiterhin das Zertifikat aus dem Keystore und hängt dieses an die Signatur der signierten JAR-Datei an. Nachdem eine signierte JAR-Datei erzeugt wurde, muss das Laufzeitsystem des Code-Empfängers in die Lage versetzt werden, die Signatur zu authentifizieren, wenn die Anwendung, die in der signierten JAR-Datei gespeichert ist, versucht, eine Datei zu lesen und dabei die Policy-Datei dem signierten Code die notwendige Berechtigung einräumt. Um die Signatur authentifizieren zu können, muss der öffentliche Schlüssel des Senders im Keystore des Empfängers gespeichert sein. Hierzu muss eine Kopie des Zertifikats gesendet werden, das den öffentlichen Schlüssel authentifiziert. Um das Zertifikat aus dem Keystore in eine Datei zu kopieren, muss die folgende Anweisung eingegeben werden. Hierbei wird wiederum das Passwort des Keystores (Fir123ma) abgefragt. Die Datei, die das Zertifikat anschließend enthält, heißt in diesem Fall StephanFischer.cer.
keytool -export -keystore beispielstore -alias signaturDatei -file StephanFischer.cer Funktionalität des Empfängers Der Empfänger, der die Anwendung ausführen möchte, geht in den folgenden Schritten vor:
Um eine Anwendung ausführen zu können, die in einer JAR-Datei gespeichert ist, muss der Empfänger die folgende Anweisung eingeben, bei der mit der Option cp (Classpath) angegeben wird, welche JAR-Datei aufgerufen werden soll. Hierbei soll die Datei gelesen werden, die unter dem Pfad ..\Hallo.txt gespeichert ist. Hierbei soll aber ein Security-Manager Verwendung finden, der mittels der Option -Djava.security.manager angegeben wird.
java -Djava.security.manager -cp sAusgabe.jar Ausgabe ..\Hallo.txt Es sollte nun unmittelbar verständlich sein, dass die Ausführung dieser Anweisung eine Exception generiert. Die Exception ergibt sich zwangsläufig, da die Anwendung versucht, auf das Dateisystem des Empfängers zuzugreifen, was bei Verwendung des Security-Managers verboten ist, da die Anwendung nicht über dementsprechende Berechtigungen verfügt. Bevor dem signierten Code die notwendigen Berechtigungen eingeräumt werden können, muss das Zertifikat des Senders als sog. Trusted Certificate in den Keystore geladen werden. Es sei nun angenommen, dass der Sender die signierte JAR-Datei sAusgabe.jar geschickt hat, die die Dateien Ausgabe.class und StephanFischer.cer enthält, wobei letztere das Zertifikat mit dem öffentlichen Schlüssel enthält, der zu dem privaten Schlüssel passt, mit dem die JAR-Datei signiert wurde. Um als Empfänger agieren zu können, muss zunächst ein Keystore namens beispielstore2 angelegt werden, in den anschließend das Zertifikat des Senders mit dem Alias-Namen stephan importiert wird. Zum Anlegen des Keystores sind die folgenden Schritte durchzuführen:
Da der Keystore noch nicht existiert, wird er angelegt, wobei ein Passwort verlangt wird. In diesem Fall wird das Passwort Fir321ma verwendet, das ansonsten beliebig gewählt werden kann. Das Werkzeug keytool gibt anschließend die Zertifikatsinformation aus und verlangt eine Verifikation, nach der im erfolgreichen Fall das Zertifikat als Trusted Certificate betrachtet wird. Die Ausgabe, die sich hierbei einstellt, ist in Abb. 5-8 angegeben. Analog kann man die Information des Zertifikats extrahieren, indem die folgende Anweisung eingegeben wird:
keytool -printcert -file StephanFischer.cer Man kann bspw. den Sender fragen, ob die Fingerprints, die Teil des Zertifikats sind, identisch mit denen sind, die der Sender verwendet hat; ob also während der Übertragung keine Modifikation des Zertifikats stattgefunden hat. Üblicherweise führt man diesen Vergleich durch, bevor man ein Zertifikat als Trusted Certificate akzeptiert.
Abb. 5.8: Importierung von Zertifikaten Nachdem das Zertifikat importiert wurde, muss eine Policy-Datei angelegt werden. An dieser Stelle sollen die notwendigen Konzepte nochmals wiederholt werden. Die Policy-Datei wird verwendet, um der empfangenen JAR-Datei die notwendigen Berechtigungen zum Lesen des Dateisystems einzuräumen. Dazu muss die JAR-Datei mit dem privaten Schlüssel signiert sein, der dem öffentlichen Schlüssel des Senders entspricht, der im Keystore des Empfängers abgelegt ist. Die Schritte zum Anlegen der Policy-Datei sind dann die folgenden:
Nach dem Aufruf des Policy-Tools muss zuerst der zu verwendende Keystore angegeben werden. Hierzu muss unter dem Menüpunkt Edit die Option Change Keystore ausgewählt werden. Es sei daran erinnert, dass der Keystore immer in Form einer URL angegeben werden muss, also bspw. als file:/C:/home/fisch/private/JavaBuch/Buchcode/Kapitel5/beispielstore2. Anschließend muss ein Eintrag in der Policy-Datei angelegt werden. Dazu ist der Button Add Policy Entry zu betätigen. Zunächst ist anzugeben, von wem der Code signiert wurde. Hierzu ist das SignedBy-Textfeld auszufüllen, bspw. mit dem Namen stephan. Wenn beliebigem Code, der von diesem Sender empfangen wurde, Rechte eingeräumt werden sollen, so ist das Feld CodeBase leer zu lassen, anderenfalls muss hier in Form einer URL eine Datei oder ein Verzeichnis angegeben werden. Um die notwendigen Berechtigungen zu setzen, wird anschließend der Button Add Permission betätigt. Im nun neu erscheinenden Fenster muss die Art der Berechtigung (im Fall dieses Beispiels FilePermission), der Dateiname, dem die Berechtigung eingeräumt wird (als absoluter Pfadname, nicht als URL), und die Art der Berechtigung (in diesem Fall READ) angegeben werden. Anschließend sollte der Anwender nicht vergessen, die Policy-Datei zu speichern. Wie bereits beschrieben wurde, kann die neu generierte Policy-Datei auf zwei Arten in die gesamten Sicherheitsregeln eingebunden werden:
An dieser Stelle soll die erste Möglichkeit verwendet werden, die Angabe der Policy-Datei in der Kommandozeilenebene. Hierzu ist die oben genannte Anweisung wie folgt zu erweitern:
java -Djava.security.manager -Djava.security.policy=policy -cp sAusgabe.jar Ausgabe ..\Hallo.txt Hierbei bezeichnet der neu hinzugekommene Ausdruck -Djava.security.policy=policy die Datei policy im momentan verwendeten Verzeichnis, die die Berechtigungen für Daten des Senders stephan enthält. Ist bei der Erstellung der Policy-Datei kein Fehler aufgetreten, so kann nun die Anwendung mit der oben angegebenen Anweisung problemlos ausgeführt werden. Datenaustausch Soll ein wichtiges Dokument elektronisch versendet werden, so sollten die Daten signiert werden. Der Empfänger ist dann in der Lage, die Konsistenz des Dokuments zu überprüfen und festzustellen, ob während der Übertragung Modifikationen aufgetreten sind. Funktionalität des Senders Die Signatur eines Dokuments erfolgt nach exakt demselben Schema, das bereits im vorangegangenen Unterkapitel erläutert wurde, also in den folgenden Schritten:
Wird das Werkzeug keytool dazu verwendet, ein Paar aus privatem und öffentlichem Schlüssel zu erzeugen, so wird ein Eintrag in einem Keystore angelegt, der den privaten Schlüssel und ein selbst signiertes Zertifikat für den öffentlichen Schlüssel enthält. Dieses Vorgehen ist nur dann adäquat, wenn die Empfänger den Sender bereits kennen und diesem vertrauen. Ein Zertifikat ist aber wesentlich vertrauenswürdiger, wenn es von einer Certification Authority (CA) unterschrieben ist. Um ein Zertifikat von einer CA unterschreiben zu lassen, muss zuerst ein Certificate Signing Request (CSR) erzeugt werden, indem die folgende Anweisung ausgeführt wird:
keytool -certreq -alias alias -file csrDatei In dieser Anweisung wird alias dazu verwendet, auf den jeweiligen Eintrag im Keystore zuzugreifen, der den privaten Schlüssel und das Zertifikat für den öffentlichen Schlüssel enthält. Der Ausdruck csrDatei bezeichnet den Namen der Datei, die hierbei erzeugt wird. Anschließend wird diese Datei an eine CA, bspw. an die Firma VeriSign, übermittelt. Die CA authentifiziert dann den Sender und übermittelt ein Zertifikat, das den öffentlichen Schlüssel des Senders in unterschriebener Form authentifiziert. Die Antwort der CA muss im Folgenden importiert werden. Wird ein Certificate Signing Request an eine Certification Authority übermittelt, so muss das Original im Keystore des Senders durch das Zertifikat, das die CA zurück schickt, ersetzt werden. Dies erfolgt, indem das übermittelte Zertifikat importiert wird. Dazu ist es jedoch zuerst notwendig, dass ein Trusted Certificate angelegt wird, mit dem der öffentliche Schlüssel der CA authentifiziert werden kann. Zum Anlegen eines Trusted Certificate muss zunächst geprüft werden (bspw. über einen Vergleich der Fingerprints), ob das Zertifikat gültig ist. Ist das empfangene Zertifikat gültig, so kann es dem Keystore mit dem folgenden Kommando hinzugefügt werden:
keytool -import -alias alias -file zertifikat.cer -keystore storename Mit Hilfe dieses Kommandos wird ein Trusted Certificate erzeugt, dessen Inhalt aus der Datei zertifikat.cer kopiert wird. Der Eintrag im Keystore enthält dann die Daten der Datei zertifikat.cer und wird mit dem angegebenen Alias-Namen referenziert. Steht ein Trusted Certificate zur Verfügung, so kann die Zertifikatsantwort der CA importiert werden, wobei das bisherige selbst signierte Zertifikat ersetzt werden muss. Die folgende Anweisung übernimmt diese Aufgabe.
keytool -import -keystore beispielstore2 -alias stephan -file antwortDerCA Funktionalität des Empfängers Der Empfänger des Dokuments geht in den folgenden Schritten vor:
Nachdem das Zertifikat mit dem öffentlichen Schlüssel des Senders als Trusted Certificate in den Keystore des Empfängers importiert wurde, kann das Werkzeug jarsigner dazu verwendet werden, die Authentizität der Signatur der JAR-Datei zu prüfen. Hierdurch wird sichergestellt, dass die Datei während der Übertragung nicht modifiziert wurde. Möchte der Empfänger bspw. prüfen, ob die Datei sAusgabe.jar kompromittiert wurde, so muss die folgende Anweisung angegeben werden:
jarsigner -verify -verbose -keystore beispielstore2 sAusgabe.jar
Abb. 5.8: Verifikation von Dokumenten Es ist zu beachten, dass das Kommando mit der Option -verbose ausgeführt werden muss, damit die notwendige Information auf der Kommandozeilenebene ausgegeben wird. In der in Abb. 5-9 dargestellten Ausgabe ist erkennbar, dass das übertragene Dokument Teil der JAR-Datei ist, die signiert wurde und ferner, dass die Signatur verifiziert wurde. Weiterhin wird angezeigt, dass sich der öffentliche Schlüssel zur Verifikation der Signatur im angegebenen Keystore befindet. Zusammenfassung In diesem Unterkapitel wurden die Sicherheitsaspekte von Java in einer detaillierten Form betrachtet, vor allem die Signatur von Daten, Policy-Dateien und die Verwendung von Keystores. Es sei jedoch darauf hingewiesen, dass diese Darstellung auf die Verwendung der Werkzeuge von Java (bspw. jar, jarsigner oder keytool) fokussiert ist. Werden die in Java enthaltenen APIs verwendet, so ist eine wesentlich weitgehendere Funktionalität realisierbar. Diese kann jedoch im Rahmen dieses Buches nicht erläutert werden. Einerseits kann der Leser die notwendigen Funktionen nur verstehen, wenn er ein Experte im Gebiet der Sicherheitstechnik ist. Andererseits wäre eine Darstellung derart umfangreich, dass sie den Rahmen dieses Buches sprengen würde. Nach der Lektüre dieses Teilkapitels sollte der Leser jedoch in der Lage sein, Sicherheitsfunktionen von Java derart verwenden zu können, dass generelle Aspekte der sicheren Datenübertragung mit Hilfe der vorgestellten Werkzeuge ohne Probleme realisiert werden können. Die Datenübertragung selbst ist Thema des folgenden Unterkapitels. |
|
|