Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 47 - Sicherheit und Kryptographie

## 47.3 Signierte Applets

- 47.3 Signierte Applets
  - 47.3.1 Ein »unerlaubtes« Applet
  - 47.3.2 Signieren des Applets
  - 47.3.3 Ex- und Import von Zertifikaten
  - 47.3.4 Anpassen der Policy-Datei
    - Policy-Dateien
    - Das Format einer Policy-Datei
    - Erstellen der Policy-Datei
  - 47.3.5 Die Klasse SecurityManager

In diesem Abschnitt wird beschrieben, wie einem Applet der Zugriff auf geschützte Ressourcen gewährt werden kann. Wir gehen dazu in folgenden Schritten vor:

1. Zunächst wird ein Applet entwickelt, das auf einige beschränkte Ressourcen zugreift. Ohne weitere Vorkehrungen ist es nicht lauffähig, sondern wird mit einer `SecurityException` abgebrochen.
2. Anschließend zeigen wir, wie das Applet mit Hilfe des in Abschnitt 47.1.6 erzeugten Schlüsselpaars signiert wird.
3. Danach demonstrieren wir die Weitergabe von Zertifikaten.
4. Schließlich zeigen wir, wie die Sicherheitseinstellungen so angepaßt werden, daß das Applet Zugriff auf die gewünschten Ressourcen erhält.

### 47.3.1 Ein »unerlaubtes« Applet

Wir wollen uns die Aufgabe stellen, ein Applet zu schreiben, das einige Sicherheitsverstöße begeht. Zunächst soll es eine Datei auf dem lokalen Computer erzeugen und einen Zeitstempel hineinschreiben. Zusätzlich soll es auf einige geschützte System-Properties zugreifen und deren Inhalt in die Datei schreiben. Das Applet hat folgenden Aufbau:

    001 /* TrustedApplet.java */
    002
    003 import java.awt.*;
    004 import java.applet.*;
    005 import java.util.*;
    006 import java.io.*;
    007
    008 public class TrustedApplet
    009 extends Applet
    010 {
    011   static final String ALLOWED_DIR = "c:\\tmp\\applets\\";
    012   static final String FNAME       = "TrustedApplet.log";
    013   static final String LOGMSG      = "Erzeugt von Applet: ";
    014   String msg;
    015
    016   public void init()
    017   {
    018     msg = "Uninitialisiert";
    019     FileWriter out = null;
    020     try {
    021       //Ausgabedatei erzeugen
    022       out = new FileWriter(ALLOWED_DIR + FNAME);
    023       //Logmessage schreiben
    024       out.write(LOGMSG);
    025       //Zeitstempel schreiben
    026       GregorianCalendar cal = new GregorianCalendar();
    027       out.write(cal.get(Calendar.DATE) + ".");
    028       out.write((cal.get(Calendar.MONTH) + 1) + ".");
    029       out.write(cal.get(Calendar.YEAR) + " ");
    030       out.write(cal.get(Calendar.HOUR_OF_DAY) + ":");
    031       out.write(cal.get(Calendar.MINUTE) + ":");
    032       out.write(cal.get(Calendar.SECOND) + "");
    033       out.write(System.getProperty("line.separator"));
    034       //System-Properties lesen und in Datei schreiben
    035       out.write(getProp("user.name"));
    036       out.write(getProp("user.home"));
    037       out.write(getProp("user.dir"));
    038       //Datei schließen
    039       msg = "Alle Sicherheitshuerden ueberwunden!";
    040     } catch (Exception e) {
    041       msg = e.toString();
    042     } finally {
    043       if (out != null) {
    044         try {
    045           out.close();
    046         } catch (IOException e) {
    047           //silently ignore
    048         }
    049       }
    050     }
    051   }
    052
    053   public void paint(Graphics g)
    054   {
    055     g.drawString(msg, 20, 20);
    056   }
    057
    058   private String getProp(String prop)
    059   {
    060     return prop + "=" +
    061            System.getProperty(prop) +
    062            System.getProperty("line.separator");
    063   }
    064 }
| TrustedApplet.java

Listing 47.7: Ein "unerlaubtes" Applet

Es versucht zunächst eine Datei c:\tmp\applets\TrustedApplet.log zu erzeugen. Wenn dies gelingt, instanziert es ein aktuelles `GregorianCalendar`-Objekt und schreibt dessen Werte in die Datei. Schließlich versucht es, die System-Properties "user.name", "user.home" und "user.dir" zu lesen und deren Werte ebenfalls in die Datei zu schreiben. Sind all diese Versuche erfolgreich, gibt das Applet die Meldung "Alle Sicherheitshuerden ueberwunden!" aus. Tritt eine Ausnahme auf, wird deren Text ausgegeben.

|  | Um das Schreiben der Datei zu ermöglichen, ist ein Verzeichnis c:\tmp\applets anzulegen. Andernfalls gibt es eine `IOException` - selbst wenn alle Sicherheitshürden genommen sind. |  |
Hinweis

Das Applet kann mit folgender HTML-Datei gestartet werden:

    001 <html>
    002 <head>
    003 <title>TrustedApplet Demo</title>
    004 </head>
    005
    006 <body>
    007 <h1>TrustedApplet Demo</h1>
    008
    009 <applet
    010   code="TrustedApplet.class"
    011   width=600
    012   height=200
    013 >
    014 TrustedApplet Demo
    015 </applet>
    016
    017 </body>
    018 </html>

Listing 47.8: Vorläufige HTML-Datei zum Aufruf des unerlaubten Applets

Das Applet kann nun beispielsweise mit dem `appletviewer` gestartet werden:

    appletviewer TrustedApplet.html

Wird es ohne weitere Vorkehrungen gestartet, scheitert es bereits am Erzeugen der Ausgabedatei und gibt eine `SecurityException` auf dem Bildschirm aus.

### 47.3.2 Signieren des Applets

Zum Signieren ist es zunächst erforderlich, alle für den Betrieb des Applets nötigen Dateien in ein jar-File zu packen. Signiert wird also nicht eine einzelne .class-Datei, sondern alle Dateien innerhalb des jar-Archivs. Dazu verwenden wir folgendes Kommando:

    jar cvf trapp.jar TrustedApplet.class

Jetzt wird ein jar-Archiv trapp.jar erstellt, das die Klassendatei TrustedApplet.class enthält. Dieses Archiv muß nun signiert werden. Dazu steht im JDK das Hilfsprogramm `jarsigner` zur Verfügung. Es arbeitet kommandozeilenbasiert und wird folgendermaßen aufgerufen:

    jarsigner -signedjar outjar injar alias

Die einzelnen Elemente haben folgende Bedeutung:

- injar ist der Name des zu signierenden jar-Archivs.
- outjar ist der Name der Ausgabedatei, also des signierten Archivs.
- alias ist der Aliasname des Eintrags in der Schlüsseldatenbank, dessen privater Schlüssel zum Signieren verwendet werden soll.

Nachdem wir in Abschnitt 47.1.6 bereits ein Schlüsselpaar mit dem Alias "hjp3" erzeugt und in der Schlüsseldatenbank abgelegt haben, muß der Aufruf von `jarsigner` so erfolgen:

    C:\--->jarsigner -signedjar strapp.jar trapp.jar hjp3
    Enter Passphrase for keystore: hjp3ks
    Enter key password for hjp3: hjp3key

Die beiden Paßwörter zum Zugriff auf die Schlüsseldatenbank und den Schlüssel werden auf Nachfrage in der Kommandozeile angegeben. Nachdem das Programm einige Zeit gerechnet hat, erzeugt es das signierte Archiv mit dem Namen strapp.jar.

|  | `jarsigner` bietet neben den hier erwähnten noch weitere Optionen, auf die wir nicht weiter eingehen wollen. Das Programm wird zusammen mit den anderen Hilfsprogrammen in der Tool-Dokumentation des JDK ausführlich beschrieben. |  |
Hinweis

Bisher wurde das Applet direkt aus der .class-Datei geladen. Um es aus einem jar-Archiv zu laden, muß das `APPLET`-Tag der HTML-Datei um das `archive`-Argument erweitert werden:

    001 <-- TrustedApplet.html -->
    002
    003 <html>
    004 <head>
    005 <title>TrustedApplet Demo</title>
    006 </head>
    007
    008 <body>
    009 <h1>TrustedApplet Demo</h1>
    010
    011 <applet
    012   archive="strapp.jar"
    013   code="TrustedApplet.class"
    014   width=600
    015   height=200
    016 >
    017 TrustedApplet Demo
    018 </applet>
    019
    020 </body>
    021 </html>
| TrustedApplet.html

Listing 47.9: Aufruf des signierten Applets

|  | Wir könnten das Applet jetzt wie zuvor starten, würden aber immer noch dieselbe `SecurityException` erhalten. Es ist zwar signiert und das Zertifikat ist auf diesem Rechner bekannt (denn hier wurde es erstellt). Die Policy-Datei ist aber noch nicht angepaßt, und daher lehnt der `SecurityManager` des JDK die Ausführung der gefährlichen Operationen nach wie vor ab. |  |
Hinweis

### 47.3.3 Ex- und Import von Zertifikaten

Soll das signierte Applet auf anderen Arbeitsplätzen laufen, ist es erforderlich, das Zertifikat des Schlüssels, mit dem es signiert wurde, dort zu installieren. Soll es dagegen nur auf dem Arbeitsplatz laufen, auf dem auch der Schlüssel generiert wurde, ist das nicht erforderlich. Bei der Generierung des Schlüssels wurde ja auch ein (selbstsigniertes) Zertifikat erzeugt.

Um das Zertifikat weitergeben zu können, muß es zunächst unter Verwendung der Option `-export` von `keytool` aus der lokalen Schlüsseldatenbank exportiert werden:

    keytool -export -alias hjp3 -file hjp3.cert

Es liegt nun in der Datei hjp3.cert und kann auf das Zielsystem kopiert werden. Mit der `-import`-Option von `keytool` kann es dort in die Schlüsseldatenbank aufgenommen werden:

    C:\--->keytool -import -alias hjp3 -file hjp3.cert
    Enter keystore password:  hjp3ks
    Owner: CN=Guido Krueger, O=Computer Books, C=de
    Issuer: CN=Guido Krueger, O=Computer Books, C=de
    Serial number: 38663e2d
    Valid from: Sun Dec 26 17:11:25 GMT+01:00 1999 until: Sat Mar 25 17:11:25 GMT+01
    :00 2000
    Certificate fingerprints:
             MD5:  D5:73:AB:06:25:16:7F:36:27:DF:CF:9D:C9:DE:AD:35
             SHA1: E0:A4:39:65:60:06:48:61:82:5E:8C:47:8A:2B:04:A4:6D:43:56:05
    Trust this certificate? [no]:  y
    Certificate was added to keystore

Nach dem Aufruf muß zunächst das Paßwort der Schlüsseldatenbank angegeben werden. Dann zeigt das Programm die Eigenschaften des Zertifikats an und erwartet, daß die Informationen bestätigt werden. Anschließend wird das Zertifikat in die Schlüsseldatenbank aufgenommen.

### 47.3.4 Anpassen der Policy-Datei

#### Policy-Dateien

Der letzte Schritt besteht darin, die Sicherheitseinstellungen auf dem Zielsystem anzupassen. Applets, die mit dem Zertifikat verifiziert werden können, das unter dem Alias "hjp3" in der Schlüsseldatenbank abgelegt wurde, sollen Dateien im Verzeichnis c:\tmp\applets lesen und schreiben und auf die System-Properties "user.name", "user.home" und "user.dir" zugreifen können.

Die Sicherheitseinstellungen des JDK werden mit Hilfe von Policy-Dateien definiert. Es gibt zwei Stellen im Dateisystem, von denen das JDK sie standardmäßig einliest:

- Die System-Policies befinden sich in der Datei java.policy im Unterverzeichnis jre\lib\security des JDK-Installationsverzeichnisses. Diese Datei braucht normalerweise nicht verändert zu werden, sie enthält die globalen Sicherheitseinstellungen.
- Die benutzerbezogenen Sicherheitseinstellungen können in der Datei .java.policy abgelegt werden. Sie liegt im Home-Verzeichnis des aktuellen Benutzers. Auf Windows-95/98-Einzelplatzsystemen liegt sie (wie die Schlüsseldatenbank) im Verzeichnis c:\windows. Diese Datei ist standardmäßig nicht vorhanden, kann aber leicht selbst angelegt werden.

|  |  |  | Policy-Dateien können auch an beliebigen anderen Stellen im Dateisystem liegen. In diesem Fall muß beim Aufruf des Java-Interpreters das System-Property "java.security.policy" mit dem Namen der zu verwendenen Policy-Datei angegeben werden. Wäre beispielsweise hjp3policy die zu verwendende Policy-Datei, so müßte der Appletviewer mit der Option "-J-Djava.security.policy=hjp3policy" aufgerufen werden. |  |
Tip

#### Das Format einer Policy-Datei

Policy-Dateien sind zeilenorientierte Textdateien, die mit einem gewöhnlichen Texteditor bearbeitet werden können. Alternativ stellt das JDK ein einfaches GUI-basiertes Hilfsprogramm mit dem Namen `policytool` zur Verfügung, mit dem Policy-Dateien erstellt und bearbeitet werden können. Auf seine Verwendung wollen wir aber nicht weiter eingehen.

Eine Policy-Datei enthält zwei Arten von Einträgen. Beide sind optional:

- Einen "keystore"-Eintrag, der die Lage der Schlüsseldatenbank angibt.
- Beliebig viele "grant"-Einträge, mit denen Berechtigungen definiert werden.

Alle Einträge werden mit einem Semikolon beendet. Kommentare können an beliebiger Stelle durch // oder /* ... */ angelegt werden.

Der "keystore"-Eintrag erwartet als Argument einen URL, der auf die Schlüsseldatenbank verweist. Auf einem Windows-98-Einzelplatzsystem sieht er beispielsweise so aus:

    keystore "file:/c:/windows/.keystore";

Die "grant"-Einträge haben folgende Syntax:

    grant [SignedBy "signer"] [, CodeBase "URL"] {
      permission permission_class [ "target" ] [, "action"]
                 [, SignedBy "signers"];
      ...
    };

Eine Berechtigung kann wahlweise an einen Unterzeichner oder eine Ladeadresse (oder beide) vergeben werden. Die Option "SignedBy" führt eine Liste von Aliasnamen auf, deren Zertifikate vorhanden sein müssen, damit die Berechtigung gewährt wird. Die Option "CodeBase" spezifiziert die Adresse, von der ein Applet geladen werden darf, um die Berechtigung zu halten. Fehlt die CodeBase-Klausel, wird nur die Unterzeichnerliste verwendet; fehlt die SignedBy-Klausel, ist es nur die Ladeadresse.

Nach einer öffnenden geschweiften Klammer folgen beliebig viele Berechtigungen, die jeweils durch das Schlüsselwort "permission" eingeleitet werden. Anschließend wird der Eintrag mit einer schließenden geschweiften Klammer abgeschlossen. Der zuvor spezifizierte Berechtigte erhält alle Berechtigungen, die zwischen den beiden Klammern angegeben sind.

Jede Berechtigung muß als erstes Argument den Namen einer Berechtigungsklasse angeben. Daneben kann sie zwei weitere Argumente target und action haben, mit denen Details spezifiziert werden. Tabelle 47.1 listet die gebräuchlichsten Berechtigungen und ihre Argumente auf. Details können (und müssen) in dem Dokument "Java Security Architecture" nachgelesen werden, das Bestandteil der JDK-Dokumentation ist.

Klasse | Zugriff auf | Target | Action
`java.io.FilePermission` | Dateien und Verzeichnisse | Datei- oder Verzeichnisnamen. Wird als letztes Zeichen ein "*" angegeben, so gilt die Berechtigung für das komplette Verzeichnis. Steht dort ein "-", so gilt sie zusätzlich für alle Unterverzeichnisse. Wird "<<ALL FILES>>" angegeben, gilt die Berechtigung für alle Dateien in allen Verzeichnissen! | read, write, delete, execute
`java.net.SocketPermission` | TCP/IP-Verbindungen | Hostname oder IP-Adresse, gefolgt von Portnummern-Bereich | accept, connect, listen, resolve
`java.util.PropertyPermission` | System-Properties | Names des Properties | read, write
`java.lang.RuntimePermission` | Die Klasse Runtime | exitVM, stopThread, loadLibrary, queuePrintJob, ... | -
`java.awt.AWTPermission` | Window-Ressourcen | accessClipboard, showWindowWithoutWarningBanner, ... | -
`java.security.AllPermission` | Alle Ressourcen | - | -

Tabelle 47.1: Wichtige Permission-Klassen

#### Erstellen der Policy-Datei

Nach diesen Vorbemerkungen können wir die Policy-Datei \windows\.java.policy erstellen. Sie hat folgenden Inhalt:

    keystore "file:/c:/windows/.keystore";

    grant SignedBy "hjp3" {
      permission java.io.FilePermission "c:\\tmp\\applets\\*", "read,write";

      permission java.util.PropertyPermission "user.name", "read";
      permission java.util.PropertyPermission "user.home", "read";
      permission java.util.PropertyPermission "user.dir", "read";
    };

Im ersten Eintrag geben wir die Position der Schlüsseldatenbank an, sie liegt im Verzeichnis c:\windows. Anschließend definieren wir die Berechtigungen für alle Applets, die mit dem Zertifikat "hjp3" signiert wurden. Zunächst erhalten sie Schreib- und Leseberechtigung im Verzeichnis c:\tmp\applets. Dort können sie ohne weitere Einschränkungen Dateien anlegen, überschreiben und lesen. Zusätzlich erlauben wir den so signierten Applets, die drei System-Properties "user.name", "user.home" und "user.dir" zu lesen.

Nun läßt sich unser signiertes Applet ohne `SecurityException` aufrufen und gibt die erlösende Meldung "Alle Sicherheitshuerden ueberwunden" aus. Im Verzeichnis c:\tmp\applets sollte sich anschließend eine Datei TrustedApplet.log befinden und etwa folgenden Inhalt haben:

    Erzeugt von Applet: 30.12.1999 20:50:40
    user.name=Guido Krüger
    user.home=C:\WINDOWS
    user.dir=C:\arc\doku\hjp3\misc

### 47.3.5 Die Klasse SecurityManager

Die Prüfung der Zugriffsberechtigungen wird mit Hilfe der Klasse `SecurityManager` aus dem Paket `java.lang` vorgenommen. Der `SecurityManager` ist ein Objekt, das entweder gar nicht oder genau einmal im laufenden Java-Programm vorhanden ist. Nach der ersten Instanzierung kann es nicht mehr geändert oder entfernt werden.

Zugriffe auf den `SecurityManager` sind an den Stellen der Laufzeitbibliothek eingebaut, an denen auf gefährliche Ressourcen zugegriffen wird. Ein Beispiel aus der Klasse `FileInputStream` sieht etwa so aus:

    ...
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
      security.checkRead(name);
    }
    //Gefährlicher Code
    ...

Zunächst wird geprüft, ob ein `SecurityManager` installiert wurde. Ist das nicht der Fall, fährt das Programm ohne Einschränkung fort. Gibt es dagegen einen `SecurityManager`, wird dessen `checkRead`-Methode aufgerufen. Sie löst eine `SecurityException` aus, wenn die gewünschte Berechtigung fehlt. Der Code hinter dem `checkRead` wird in diesem Fall nicht mehr erreicht. Ist die Berechtigung dagegen vorhanden, wird `checkRead` ohne weitere Aktionen beendet und der dahinter liegende Code ausgeführt.

Applets besitzen grundsätzlich einen `SecurityManager`. Der AppletViewer bzw. Web-Browser sorgen während der Initialisierung für dessen Instanzierung. Applikationen dagegen haben normalerweise keinen `SecurityManager` (das ist der Grund, weshalb in Applikationen alle gefährlichen Operationen erlaubt sind). Soll eine Applikation einen `SecurityManager` erhalten, so kann sie entweder mit der Option "-Djava.security.manager" gestartet werden, oder der `SecurityManager` kann im Programm selbst installiert werden:

    ...
    System.setSecurityManager(new SecurityManager());
    ...

Das folgende Beispielprogramm gibt den Inhalt des System-Properties "user.name" aus. Normalerweise kann es ohne Fehler ausgeführt werden:

    001 /* SecuMgrTest.java */
    002
    003 public class SecuMgrTest
    004 {
    005   public static void main(String[] args)
    006   {
    007     System.out.println(
    008       "user.name is " + System.getProperty("user.name")
    009     );
    010   }
    011 }
| SecuMgrTest.java

Listing 47.10: Ausgeben des System-Properties "user.name"

Läuft es dagegen unter Kontrolle eines SecurityManagers, so führt der Aufruf zu einer `SecurityException`:

    C:\--->java -Djava.security.manager SecuMgrTest
    Exception in thread "main"
      java.security.AccessControlException: access denied
      (java.util.PropertyPermission user.name read)
            at java.security.AccessControlContext.checkPermission(
              AccessControlContext.java:276)
            at java.security.AccessController.checkPermission(
              AccessController.java:403)
            at java.lang.SecurityManager.checkPermission(
              SecurityManager.java:549)
            at java.lang.SecurityManager.checkPropertyAccess(
              SecurityManager.java:1242)
            at java.lang.System.getProperty(System.java:555)
            at Test1.main(SecuMgrTest.java:7)

Bei Bedarf kann man Applikationen auf diese Weise mit denselben Sicherheitsmechanismen ausstatten wie Applets. jar-Dateien, aus denen Applikationen geladen werden, lassen sich ebenso signieren wie solche, aus denen Applets geladen werden. Die in der Policy-Datei definierten Rechte gelten dann für die daraus gestartete Applikation.

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de