Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 24 Sicherheitskonzepte
gp 24.1 Der Sandkasten (Sandbox)
gp 24.2 Sicherheitsmanager (Security Manager)
gp 24.2.1 Der Sicherheitsmanager bei Applets
gp 24.2.2 Sicherheitsmanager aktivieren
gp 24.2.3 Wie nutzen die Java-Bibliotheken den Sicherheitsmanager?
gp 24.2.4 Rechte vergeben durch Policy-Dateien
gp 24.2.5 Erstellen von Rechte-Dateien mit dem grafischen Policy-Tool
gp 24.2.6 Kritik an den Policies
gp 24.3 Dienstprogramme zur Signierung
gp 24.3.1 Mit keytool Schlüssel erzeugen
gp 24.3.2 Signieren mit jarsigner
gp 24.4 Digitale Unterschriften
gp 24.4.1 Die MDx-Reihe
gp 24.4.2 Secure Hash Algorithm (SHA)
gp 24.4.3 Mit der Security-API einen Fingerabdruck berechnen
gp 24.4.4 Die Klasse MessageDigest
gp 24.4.5 Unix-Crypt
gp 24.5 Verschlüsseln von Datenströmen


Galileo Computing

24.2 Sicherheitsmanager (Security Manager)downtop

Wir wissen bereits über Applets, dass sie auf einige Ressourcen des Rechners nicht zugreifen dürfen. Zwischen dem Aufrufer einer Bibliotheksfunktion und dem Betriebssystem sitzt eine Einheit, die unsere Aktionen genau kontrolliert. Dieses Zwischensystem ist der Sicherheitsmanager. Standardmäßig startet die Laufzeitumgebung für Applikationen ohne Sicherheitsmanager, aber für Applets gibt es spezielle Sicherheitsmanager. Dies sind Exemplare von Unterklassen der abstrakten Klasse SecurityManager. Der aktuelle Sicherheitsmanager lässt sich mit der Methode getSecurityManager() aus der Klasse java.lang.System bestimmen. Führen wir folgende Zeile in einer Applikation aus, so sehen wir, dass anfänglich kein Sicherheitsmanager gesetzt ist, denn die Ausgabe ist null:

Abbildung
Hier klicken, um das Bild zu Vergrößern

public class ApplicationSecManager
{
  static public void main( String args[] )
  {
    System.out.println( System.getSecurityManager() );
  }
}

Im Gegensatz dazu sehen wir bei Applets ein etwas anderes Bild. Nachfolgendes Programm liefert eine Ausgabe wie sun.applet.AppletSecurity@123456 auf dem Bildschirm. Die Zahl ist der Hashcode des Objekts:

public class Applet1 extends java.applet.Applet
{
  public void paint( java.awt.Graphics g )
  {
    g.drawString( System.getSecurityManager().toString(), 10, 10 );
  }
}

Galileo Computing

24.2.1 Der Sicherheitsmanager bei Appletsdowntop

Bei den beiden Beispielen ist deutlich geworden, dass während der Initialisierung eines Applets ein SecurityManager gesetzt wird und bei Applikationen nicht. Doch was ist einem Applet eigentlich verboten?

Applets dürfen nicht auf lokale Dateien des Client-Rechners zugreifen. Applets dürfen nicht einfach Dateien löschen oder lesen. Auf diese Weise könnten sie etwa wichtige Systemdateien an ihren Heimatserver übermitteln und damit ein ernsthaftes Risiko darstellen. All dies ist leider mit vielen Unannehmlichkeiten für den Programmierer eines Applets verbunden, da wir etwa auf die Möglichkeit verzichten müssen, temporäre Dateien anzulegen. Programmieren wir beispielsweise einen HTML-Editor als Applet, so müssen entweder alle Operationen im Speicher durchgeführt werden oder die Daten müssen auf dem Heimatserver des Applets gelagert werden.

Applets können Dateien nicht erzeugen, modifizieren oder löschen. Folgende Anweisungen sind daher ungültig:

new File( "finanzen.doc" ).delete();                     // nicht löschen
OutputStream out = new FileOutputStream( "writer.txt" ); // nicht erzeugen
out.write( 'U' );
new File("old").renameTo( new File("new") );             // nicht umbenennen
new File( "noten.dat" ).exists();                        // kein Existenztest
new File( "newdir" ).mkdir();                            // keine Verzeichnisse anlegen
String list[] = new File( "c:\\" ).list();               // keine Auflistung
File checkFile = new File( "file" );
long length = checkFile.length();                        // keine Dateiattribute
boolean isFile = checkFile.isFile();
long lastMod = checkFile.lastModified();

Es dürfen keine Netzwerkverbindungen zu anderen Computern als dem Heimatserver aufgenommen werden. Dies gilt für alle Verbindungen, die mit den Netzwerk-Klassen URL, Socket und DatagramSocket möglich sind. Dies ist oftmals ein Einschnitt, da etwa ein Börsenticker-Applet Kursdaten von einem anderen Server holen möchte. Das Internet als verteiltes System sollte auch vollständig genutzt werden können. Für ein Applet des Heimatservers »send-a-tutor« ist Folgendes nicht möglich:

URL web = new URL( ">http://reality.tv/"; ); // Keine URL-Objekte
URLConnection conn = web.openConnection();
Socket mailSocket = new Socket( "trau.mich.nich", 25 ); // Keine Sockets
InetAddress inet = new InetAddress( "www.boese.tv" );
byte buffer[] = new byte[100];
DatagramPacket packet;                    // keine UDP Datagramme
packet = new DatagramPacket( puffer, 100, inet, 7 );
DatagramSocket sendSocket = new DatagramSocket();
sendSocket.send( packet );

Da Applets keine Verbindungen zu Fremdrechnern aufbauen können, können sie natürlich auch nicht als Server fungieren:

ServerSocket server = new ServerSocket( 8080 );
server.accept();

Die verschiedenen Factory-Klassen aus dem Net-Paket sind mit Einschränkungen programmiert, so dass sie spezielle Protokoll-Handler, Content-Handler und Sockets verwenden. Ein Applet kann die Klassen URLStreamHandlerFactory, ContentHandlerFactory und SocketImplFactory nicht überschreiben.

Ein Applet kann keine Programme ausführen, die auf dem lokalen Rechner liegen:

Runtime.getRuntime().exec( "DEL  C:\\AUTOEXEC.BAT" );

Applets dürfen auch keine anderen Programme starten und ebenfalls keine nativen Bibliotheken laden. (Für normale System-DLLs bringt das auch nichts, da sie nicht die benötigte Namenskonvention haben.)

Runtime.getRuntime().loadLibrary( "system32.dll" );

Ein etwas anderes Problem stellen Threads dar. Da alle Applets gemeinsam in einer JVM laufen, muss gewährleistet sein, dass sich nur die Threads eines Applets (also die Threads in der Thread-Gruppe des Applets) beeinflussen dürfen. In der Vergangenheit gab es mehrfach Sicherheitsprobleme, bei denen sich Threads verschiedener Applets gegenseitig stören konnten.

Auch darf kein Applet die gemeinsam genutzte virtuelle Maschine beenden:

Runtime.getRuntime().exit(0);             System.exit(0);

Applets dürfen keinen eigenen Sicherheitsmanager installieren. Den Grund haben wir schon kennen gelernt: Könnte das Applet einen neuen Sicherheitsmanager zuweisen, ließen sich leicht die für Applets geltenden Beschränkungen aushebeln. Der Java-fähige Web-Browser erzeugt für uns ein spezielles ClassLoader-Objekt, das abgesichert arbeitet.

Die Java-Umgebung setzt automatisch einige Properties, damit unser Programm etwas über seine Umgebung erfahren kann. Diese Variablen lassen sich über System.getProperty (String) auslesen. Applets dürfen nur manche Variablen lesen. Sie haben keinen Zugriff auf Informationen über das Java-Home-Directory, den Java-Klassenpfad, den User-Name, das Home-Verzeichnis und das Arbeitsverzeichnis des Anwenders. Die anderen Daten in den Variablen sind frei. Dazu gehören etwa:

gp Die Java-Versionsnummer: java.version
gp E Der Hersteller der Java-Implementierung, der String: java.vendor
gp E Die URL des Herstellers: java.vendor.url
gp Die Versionsnummer des Klassen-Dateiformats, das die JVM verarbeiten kann: java. class.version
gp E Der Betriebssystemname: os.name
gp E Die Betriebssystemarchitektur: os.arch
gp E Die Betriebssystemversion: os.version
gp E Der Dateitrenner (zwischen Verzeichnis- und Dateinamen): file.separator
gp E Der Pfadtrenner (zwischen den einzelnen Einträgen eines Suchpfades): path.separator,
gp Das Zeilenendezeichen: line.separator

Obwohl diese Daten natürlich für statistische Zwecke missbraucht werden können, sind sie doch für ein Applet unter Umständen lebensnotwendig. Denn so kann es etwa einfach an der Versionsnummer ablesen, ob eine bestimmte Klasse mit Methoden bestimmter Versionen implementiert ist oder nicht.


Galileo Computing

24.2.2 Sicherheitsmanager aktivierendowntop

Ein Sicherheitsmanager wird durch den Schalter -Djava.security.manager bei der Laufzeitumgebung aktiviert. Nehmen wir ein Programm SecTest. Wir starten es einmal mit und einmal ohne den Schalter -Djava.security.manager.

Listing 24.1 SecTest.java

import java.io.*;
public class SecTest
{
  public static void main( String args[] )
  {
    System.err.println( System.getSecurityManager() );
    System.err.println( new File("c:/address.txt").length() );
  }
}

Wenn wir das Programm ohne Schalter aufrufen, läuft es durch und gibt die Länge der Datei aus. Ist der Schalter gesetzt, wird ein Sicherheitsmanager angemeldet. Wenn dann eine potenziell kritische Operation ausgeführt wird, wie das Erfragen der Dateilänge, steigt die Laufzeitumgebung mit einer Exception aus:

$ java -Djava.security.manager SecTest
java.lang.SecurityManager@26b249
Exception in thread "main" java.security.AccessControlException: access denied (
java.io.FilePermission c:\address.txt read)
        at java.security.AccessControlContext.checkPermission(AccessControlConte
xt.java:270)
      at java.security.AccessController.checkPermission(AccessController.java:401)
      at java.lang.SecurityManager.checkPermission(SecurityManager.java:542)
      at java.lang.SecurityManager.checkRead(SecurityManager.java:887)
      at java.io.File.length(File.java:790)
      at SecTest.main(SecTest.java:9)

Galileo Computing

24.2.3 Wie nutzen die Java-Bibliotheken den Sicherheitsmanager?downtop

Ein Sicherheitsmanager hat die Kontrolle über alle problematischen (also gefährlichen) Methoden in der Java-Bibliothek. Alle Methoden, die nun irgendetwas mit Sicherheit zu schaffen haben, fragen vorher den Sicherheitsmanager, ob sie überhaupt zu der kritischen Aktion berechtigt sind. Sehen wir uns dazu die Methode list() aus dem IO-Paket in der Klasse File an:

public String[] list() {
  SecurityManager security = System.getSecurityManager();
  if (security != null)
    security.checkRead( path );
  return fs.list( this );
}

Wir erkennen, dass zunächst der Sicherheitsmanager konsultiert und dann erst die wahre Aktion ausgeführt wird. Diese steckt in der nativen Methode list0(). Die Namen fast aller nativen Methoden in der Bibliothek enden mit »0« und sind so leicht zu erkennen. Betrachten wir das Beispiel, so ist dies typisch für alle anderen Methoden aus der File-Klasse und macht deutlich, dass es keine Möglichkeit gibt, um diesen Sicherheitsmanager herumzukommen. Es sei denn, es ließe sich die Methode list0() direkt aufrufen. Dies ist aber nicht möglich, da sie fest als private native Methode in der Klasse File verankert ist. Sogar Unterklassen können list0() also weder sehen noch ausführen.

Ebenso wie die File-Klasse mit dem SecurityManager arbeitet, rufen auch andere Klassen Prüfmethoden auf, um zu entscheiden, ob der Zugriff auf eine Ressource erlaubt ist. Wenn sie vom Sicherheitsmanager verweigert wird, erfahren wir dies durch eine SecurityException. Hier ein paar Beispiele für Beschränkungen: Toolkit (können wir mit getSystemEventQueue() die Systemschlange für Ereignisse abfragen?), Window (erzeugt in einem Applet zusätzlich die Einblendung »Warning: Applet Window«), FileInputStream (dürfen wir so ein Objekt überhaupt erzeugen?), Class (dürfen wir auf Elemente der Klasse zugreifen?), ClassLoader (können wir einfach einen neuen Klassenlader definieren?), Runtime (können wir mit exit() aussteigen oder externe Programme ausführen?).


Galileo Computing

24.2.4 Rechte vergeben durch Policy-Dateiendowntop

Um einem Programm die passenden Rechte zu geben - und damit in unserem Fall Zugriff auf die Dateilänge zu bekommen -, werden die zugesicherten Rechte in einer Rechte-Datei gesammelt. Diese Datei nennt sich Policy-Datei. Wenn die Laufzeitumgebung gestartet wird, wird ein Verweis auf diese Policy-Datei mitgegeben und über diesen Weg wird der Security-Manager Berechtigungen vergeben.

Die Policy-Dateien bestehen aus einer Reihe von grant-Anweisungen. Sehen wir uns die Datei myPol.policy an, die für Dateien eine Leseberechtigung vergibt.

Listing 24.2 myPol.policy

grant {
 permission java.io.FilePermission   "<<ALL FILES>>",  "read";
};

Jetzt muss diese Berechtigungsdatei mit dem Sicherheitsmanager verbunden werden. Das geschieht am komfortabelsten von der Kommandozeile aus.

$ java -Djava.security.manager -Djava.security.policy= myPol.policy SecTest
java.lang.SecurityManager@26b249
28

Wir sehen, dass das Programm nun durchläuft und die Dateilänge ausgibt. Der Schalter -Djava.security.policy gibt einen Pfad auf die Berechtigungsdatei an. Die Datei muss im Pfad gefunden oder absolut adressiert werden.

Neben diesen benutzerdefinierten Rechteregeln gibt es systemvergebene Policy-Dateien. Sie werden von Java standardmäßig in der Datei .java.policy im Unterverzeichnis lib/security (etwa C:\Programme\jdk1.4\jre\lib\security) der Java-Installation gespeichert. Diese Rechte-Datei definiert damit die »Standardberechtigungen«. Eigene, programmunabhängige Rechte-Dateien, können unter dem Namen .java.policy im Benutzerverzeichnis abgespeichert werden. Unter Unix ist es das Verzeichnis $HOME. Auch diese Dateien können vom Systemverwalter angepasst werden. Zunächst werden die Standardsicherheitsdateien genutzt und die Benutzerdateien »nachgeladen«. Die Datei java.security im Verzeichnis security beschreibt, welche Rechte-Dateien genutzt werden. Genau dort finden sich die beiden Dateien.

Listing 24.3 \jdk1.4\jre\lib\security\java.security

# The default is to have a single system-wide policy file,
# and a policy file in the user's home directory.
policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy

Da Rechte nur vergeben, aber bisher nicht genommen werden können, besteht das Sicherheitsproblem darin, dass eine eigene Policy-Datei alle Rechte vergibt, wogegen die Systemdatei Einschränkungen vorsieht. In diesem Fall kann der Programmverwalter auch dem Benutzer das Recht nehmen, eigene Rechte-Dateien anlegen zu können. Dazu editiert er die Datei java.security. Der Eintrag allowSystemProperty muss false sein.

# whether or not we allow an extra policy to be passed on the command line
# with -Djava.security.policy=somefile. Comment out this line to disable
# this feature.
policy.allowSystemProperty=true

Alle Bibliotheken, die in das ext-Verzeichnis gelegt werden, bekommen alle Rechte. Das liegt an den Systemrechten. In java.policy, der Standard-Rechte-Datei vom System, sehen wir:

// Standard extensions get all permissions by default
grant codeBase "file:${java.home}/lib/ext/*" {
 permission java.security.AllPermission;
};

Das zeigt auch die Verwendung einer CodeBase. Sie spezifiziert den genauen Pfad, in dem die Rechte gelten. Also werden nur für das lib/ext-Verzeichnis alle Rechte vergeben, aber nicht für alle anderen Verzeichnisse.


Galileo Computing

24.2.5 Erstellen von Rechte-Dateien mit dem grafischen Policy-Tooldowntop

Das grafische Dienstprogramm policytool bietet uns die Möglichkeit, Applikationen und signierten Applets spezielle Rechte einzuräumen oder zu verweigern. Das Policy-Tool nimmt uns die Arbeit ab, von Hand die Rechte-Dateien zu editieren.

Nach dem Aufruf des Programms policytool öffnet sich ein Fenster, welches uns einige Menüpunkte bereitstellt, über die wir bestehende Rechte-Dateien editieren oder auch neue anlegen können.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 24.1 Das grafische Policy-Tool

Neue Einträge für die Zugeständnisse der Laufzeitumgebung an das Programm werden über das Menü Add Policy Entry gemacht. Über das Dialog-Fenster können anschließend eine Reihe von erlaubten Eigenschaften ausgewählt werden, dann Permissions. Folgende Tabelle zeigt einige Permissions und ihre Bedeutungen:


AllPermission Die Anwendung oder das Applet dürfen alles
FilePermission Zugriff auf Dateien und Verzeichnisse
NetPermission Zugriff auf Netzwerkressourcen
PropertyPermission Zugriff auf Systemeigenschaften
ReflectPermission Zugriff über Reflection auf andere Objekte erlauben
RuntimePermission Einschränkungen von Laufzeitsystemen wie Klassenlader
SecurityPermission Einstellen eines allgemeinen Sicherheitskonzepts, etwa für den Zugriff auf Policies
SerializablePermission Beschränkung der Serialisierung
SocketPermission Spezielle Einschränkungen an einer Socket-Verbindung


Galileo Computing

24.2.6 Kritik an den Policiestoptop

Die Policies sind eine nette Sache, um für eine Applikation die Rechte einzuschränken oder zu vergeben, doch ganz ohne Kritik ist das System nicht.

Format der Policy-Dateien

Policy-Dateien sind standardmäßig Textdateien auf der Client-Seite des Anwenders. Ein Anwender kann ohne große Probleme diese Textdateien ändern und mehr Rechte zugestehen. Die Rechte-Dateien sind nicht durch eine Prüfsumme gesichert, so dass Änderungen erkannt werden können. Zum anderen ist das Textformat nicht mehr so »modern«, und XML-basierte Textformate lösen die proprietären Dateiformate ab.

Da sich das Sicherheitssystem von Java jedoch beliebig erweitern lässt, lassen sich die Standardtextformate durch ein neues System ersetzen, welches etwa XML-Dateien liest und einen Prüfsumme (mit einer digitalen Signatur gesichert) speichert, die nicht mehr so leicht verändert werden kann.

Kein Refresh

Wurden Policy-Dateien einmal vom System eingelesen, dann können sie zwar nachträglich verändert werden, aber das Java-System erkennt diese Änderung nicht. Die Konsequenz ist, dass die Applikation neu gestartet werden muss. Das ist für Benutzerapplikationen kein großes Dilemma, doch für serverseitige Applikationen ein großes Problem. Es ist unmöglich, für eine kleine Änderung an den Policy-Dateien etwa den EJB-Server runterzufahren und wieder neu zu starten. Unter der Annahme, dass das System ein Five-Nine-System ist, also eine Verfügbarkeit von 99,999% aufweist, würde dies eine erlaubte Ausfallzeit von etwa fünf Minuten ausmachen - bei laufender Änderung der Policies kaum zu erreichen.

Keine Rollen bei der Rechtevergabe

Die hauptsächliche Idee des Policy-Verfahrens ist, einem Programm Rechte zuzuordnen. Die Rechte können weiterhin vergeben werden für Programme, die von einem bestimmten Ort kommen (CodeSource) und von einem bestimmten Anwender signiert sind (SignedBy). Was wäre, wenn wir einem bestimmten Benutzer Rechte zuordnen wollten? Das ist so einfach nicht möglich. Es müsste dann jeder Benutzer das Jar-Archiv signieren, und in der Policy-Datei stände, was jeder Benutzer machen könnte. Doch dies taugt nichts! Wenn ein Benutzer hinzukommen würde, müsste das Archiv neu signiert werden - bei 10.000 Benutzern undenkbar. Des Weiteren könnte der Benutzer selbst seine Rechte erweitern, was nicht Sinn der Sache wäre.

Gebraucht wird also nicht nur ein Verfahren, welches nach den Quellen unterscheidet, sondern auch nach Rollen. Dieses Verfahren nennt sich rollenbasierte Rechtevergabe. Dabei ist jedem Benutzer eine Rolle zugeordnet, und auf Grund dieser Rolle sind ihm Rechte zugewiesen. Das Betriebssystem Windows nutzt zum Beispiel diesen Typ der Rechtevergabe.

Sun hat die Notwendigkeit für ein rollenbasiertes System erkannt und hat JAAS (Java Authentication and Authorization Service) entworfen. JAAS ist Bestandteil von Java 1.4. Die beiden Hauptteile sind:

gp Authentifikation/Authentifizierung. Gibt die Identität gegenüber einem Kommunikationspartner an.
gp Autorisierung. Sicherstellen der Rechte, so dass ein Benutzer Aktionen durchführen kann

Wichtig in diesem Zusammenhang sind Login-Module. Sie erlauben die Anmeldung an einer Instanz, so dass sich der Benutzer authentifizieren kann. Dies kann ein komplexes System wie Kerberos sein, aber auch eine Smart-Card oder ein biometrisches System.





Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de