original in en Chianglin Ng
en to de Jürgen Pohl
Ich lebe in Singapore, ein modernes, vielrassiges Land in Südostasien. Linux benutze ich seit ungefähr zwei Jahren. Mit Red Hat 6.2 fing ich an, jetzt benutze ich Red Hat 8.0 zu Hause. Gelegentlich arbeite ich auch mit Debian Woody.
Die nachfolgenden Anweisungen gelten für Red Hat 8.0, das allgemeine Konzept ist auch für andere Distributionen anwendbar. Du mußt jedoch PostgreSQL und die dazugehörigen JDBC-Treiber installieren. Bei Red Hat 8.0 kannst du dafür den rpm-Befehl oder das Management -Tool benutzen. Du mußt auch Sun's JDK 1.4.1 herunterladen und installieren. Dank der US-Exportbestimmungen kommt Sun's JDK mit eingeschränkten Verschlüsselungsmöglichkeiten. Für unbegrenzte Verschlüsselung kannst du die Dateien mit den JCE (Java Cryptographic Extensions) herunterladen. Auf Sun's Java Website findest du weitere Einzelheiten.
Ich habe JDK 1.4.1 in /opt installiert und die JAVA_HOME -Environment-Variable zeigt auf mein JDK-Verzeichnis. Mein PATH habe ich ebenfalls aktualisiert, dort befinden sich jetzt die ausführbaren Dateien von JDK. Hier die Zeilen, die ich meiner .bash_profile - Datei hinzugefügt habe.
JAVA_HOME = /opt/j2sdk1.4.1_01
PATH = /opt/j2sdk1.4.1_01/bin:$PATH
export JAVA_HOME PATH
Die begrenzten Verschlüsselungsdateien von Sun JDK habe ich durch die uneingeschränkten JCE-Dateien ersetzt. Damit Java die JDBC-Treiber für Postgres finden kann, kopierte ich diese in mein Java-Unterverzeichnis (/opt/j2sdk1.4.1_01/jre/lib/ext). In Red Hat 8.0 sind die Postgres-JDC-Treiber in /usr/share/pgsql zu finden.
Falls das deine erste Postgres-Installation ist, musst du eine neue Datenbasis und ein neues Postgresql - Benutzerkonto einrichten. Als erstes mit su nach root und den Postgres-Service starten, dann zum Default - Postgres - Administratorkonto.
su root
password:******
[root#localhost]#/etc/init.d/postgresql start
[root#localhost]# Starting postgresql service: [ OK ]
[root#localhost]# su postgres
[bash]$
Einrichten eines neuen Postgres-Konto und einer Datenbasis.
[bash]$:createuser
Enter name of user to add: chianglin
Shall the new user be allowed to create databases? (y/n) y
Shall the new user be allowed to create more new users? (y/n)
y
CREATE USER
[bash]$createdb chianglin
CREATE DATABASE
Mein neu eingerichtes Postgres-Administrator-Konto korrespondiert mit meinem Linux-Benutzerkonto und einer Datenbasis gleichen Namens. Das psql - Werkzeug verbindet sich in der Grundeinstellung mit der Datenbasis, die mit dem gegenwärtigen Linux-Benutzerkonto korrespondiert. Weitere Einzelheiten zur Verwaltung von Konten und Datenbanken sind im Postgres-Handbuch zu finden. Zur Eingabe deines Passworts für das neue Konto, starte psql und gib den Befehl ALTER USER ein. Melde dich in deinem normalen Benutzerkonto an und starte psql. Gib folgendes ein
ALTER USER chianglin WITH PASSWORD 'test1234' ;
Um die tcpip -Verbindungen zu ermöglichen, bearbeite postgresql.conf und stelle die tcpip_socket-Option auf "true". In Redhat 8.0 ist diese Datei in /var/lib/pgsql/data zu finden. Wechsle in root und mach die folgende Einstellung
tcpip_socket=true
Der letzte Schritt ist die Bearbeitung der Datei pg_hba.conf. Sie bestimmt, welche Hosts sich mit der Postgres-Datenbank verbinden können. Mit einer einzigen Eingabe für den Host habe ich die Loop-Adresse angepasst, einschliesslich Authentifizierung mittels Passwort. Die Anpassung der Datei muss von root aus erfolgen.
host sameuser 127.0.0.1 255.255.255.255 password
Mit dem Neustart von Postgres werden die neuen Einstellungen
aktiv.
Nach den vorangegangenen Schritten ist Postgres bereit, unsichere JDBC-Verbindungen zu akzeptieren. Der Fernzugriff auf Postgres muss über ein Proxy (Relay) erfolgen.
Die folgende Darstellung zeigt, wie die Proxy-Übergabe (Relay) funktionieren sollte.
Die JDPC-Anwendung verbindet sich mit dem Client-Proxy, welcher dann alle Daten über eine SSL-Verbindung auf den entfernten Proxy-Server übermittelt. Der Proxy-Server schickt einfach alle Pakete zu Postgres und die Antwort erfolgt über die SSL-Verbindung zurück an den Client-Proxy, der sie zur JDBC-Anwendung überträgt. Der gesamte Vorgang geschieht transparent für die JDBC-Anwendung.
Wie in der Abbildung angedeutet, besteht für den Server
die Notwendigkeit, die eintreffenden Daten in einem sicheren
Stream zu empfangen und an den lokalen Ausgabe-Stream zu
übergeben, der mit dem aktuellen Server verbunden ist.
Umgekehrt trifft das auch zu: du benötigst die Daten vom
lokal eintreffenden Stream des aktuellen Servers und leitest
diese zum sicher übertragenden Stream. Das gleiche Schema
gilt für den Client - es kann mittels Threads
durchgeführt werden. Die folgende Abbildung macht das
deutlich.
Eine SSL-Verbindung erfordert Server-Authentifizierung.
Client-Authentifizierung ist wahlweise. In unserem Fall ziehe
ich beides vor, d.h. ich muss Zertifikate und Schlüssel
für den Server und den Client anlegen. Dafür benutze
ich das Keytool im Java JDK. Auf dem Client und dem Server lege
ich zwei Schlüsselspeicher an. Der erste Speicher wird
für den privaten Schlüssel des Hosts und der zweite
für die Zertifikate, denen der Host vertraut, angelegt.
Nachfolgend wird gezeigt, wie man einen Schlüsselspeicher,
einen privaten Schlüssel und ein öffentliches,
selbstbestätigendes Zertifikat für den Server
anlegt.
keytool -genkey -alias serverprivate -keystore
servestore -keyalg rsa -keysize 2048
Enter keystore password: storepass1
What is your first and last name?
[Unknown]: ServerMachine
What is the name of your organizational unit?
[Unknown]: ServerOrg
What is the name of your organization?
[Unknown]: ServerOrg
What is the name of your City or Locality?
[Unknown]: Singapore
What is the name of your State or Province?
[Unknown]: Singapore
What is the two-letter country code for this unit?
[Unknown]: SG
Is CN=ServerMachine, OU=ServerOrg, O=ServerOrg, L=Singapore,
ST=Singapore, C= [no]: yes
Enter key password for <serverprivate>
(RETURN if same as keystore password): prikeypass0
</serverprivate>
Hier ist zu bemerken, dass das Passwort zweimal benötigt wird. Erstens für den Schlüsselspeicher und zweitens für den privaten Schlüssel. Danach exportiere das öffentliche Zertifikat des Servers, welches der Client zur Authentifizierung des Servers benutzen wird, in eine Datei.
keytool -export -alias serverprivate -keystore -rfc servestore -file server.cer
Hiermit exportieren wir des Servers selbstbestätigendes, öffentliches Zertifikat in die Datei server.cer. Auf Seiten des Client importieren wir diese Datei in einen Schlüsselspeicher, der alle öffentlichen Zertifikate enthält, denen der Client vertraut.
keytool -import -alias trustservercert -file server.cer -keystore clienttruststore
Mit diesem Befehl wird das öffentliche Zertifikat des Servers in einen Schlüsselspeicher namens clienttruststore importiert. Falls dieser noch nicht besteht, wird er erzeugt und du wirst aufgefordert, ein Passwort für den Speicher einzugeben.
Jetzt ist dein System bereit, eine SSL-Verbindung mittels
Server-Authentifizierung herzustellen.
Da ich auch den Client authentifizieren will, muss ich auch
einen privaten und einen öffentlichen Schlüssel
für den Client in einem neuen
Client-Schlüsselspeicher einrichten, danach das
öffentliche Zertifikat des Client in einen neuen
Server-Schlüsselspeicher auf dem Server exportieren.
Am Ende dieses Prozesses sollten auf dem Server und dem
Client je zwei Schlüsselspeicher zu finden sein, einer
enthält den privaten Schlüssel, der andere das
vertraute Zertifikat.
Um das nachfolgende Code-Beispiel ausführen zu können, ist es notwendig, das gleiche Passwort für jeden der Schlüsselspeicher auf der entsprechenden Maschine einzurichten. Das bedeutet, die zwei Schlüsselspeicher des Servers sollten das gleiche Passwort haben, das gleiche gilt für die beiden Schlüsselspeicher des Client.
Weitere Informationen zum keytool sind in Sun's Dokumentation zu finden.Meine Klassen werden von Sun's Java Secured Socket Extensions Gebrauch machen. Die JSSE - Referenz finden wir hier. Für eine SSL-Verbindung benötigst du die Instanz eines SSL-Kontext-Objekts, das von JSSE geliefert wird. Initialisiere diesen SSL-Kontext mit den gewünschten Einstellungen und du erhältst daraus eine Secured SocketFactory - Klasse. Mit der SocketFactory kann man die SSL- Sockets erzeugen.
Für meine Anwendung wird eine Client- und eine Server-
Proxy-Klasse zur Zusammenstellung des SSL-Tunnel benötigt.
Da sie beide eine SSL-Verbindung benutzen werden, werden sie
eine SSL-Verbindungsklasse erben. Diese Klasse wird
dafür zuständig sein, den ursprünglichen
SSL-Kontext einzurichten, der vom Client - sowie vom
Server-Proxy benutzt werden wird. Zusätzlich
benötigen wir noch eine weitere Klasse, um die
übertragenden Threads aufzubauen. Insgesamt also vier
Klassen.
Hier ein Codeabschnitt aus der SSL-Verbindungsklasse
Codeabschnitt aus der SSL-Verbindungsklasse
/* initKeyStore method to load the keystores
which contain the private key and the trusted certificates
*/
public void initKeyStores(String key , String trust , char[]
storepass)
{
// mykey holding my own certificate and
private key, mytrust holding all the certificates that I
trust
try {
//get instances of the Sun JKS
keystore
mykey = KeyStore.getInstance("JKS" ,
"SUN");
mytrust = KeyStore.getInstance("JKS",
"SUN");
//load the keystores
mykey.load(new
FileInputStream(key) ,storepass);
mytrust.load(new FileInputStream(trust) ,storepass
);
}
catch(Exception e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
/* initSSLContext method to obtain a SSLContext and
initialize it with the SSL protocol and data from the keystores
*/
public void initSSLContext(char[] storepass , char[] keypass)
{
try{
//get a SSLContext from Sun JSSE
ctx = SSLContext.getInstance("TLSv1" , "SunJSSE")
;
//initializes the keystores
initKeyStores(key , trust , storepass) ;
//Create the key and trust manager
factories for handing the cerficates
//in the key and trust stores
TrustManagerFactory tmf =
TrustManagerFactory.getInstance("SunX509" ,
"SunJSSE");
tmf.init(mytrust);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance("SunX509" ,
"SunJSSE");
kmf.init(mykey , keypass);
//initialize the SSLContext with the data from
the keystores
ctx.init(kmf.getKeyManagers() ,
tmf.getTrustManagers() ,null) ;
}
catch(Exception e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
Die initSSL-Kontext-Methode erzeugt einen SSL-Kontext
mittels Sun's JSSE. Dabei kannst du das gewünschte
SSL-Protokoll angeben, ich habe TLS Version 1 (Transport Layer
Security) ausgewählt. Sobald wir eine Instanz des
SSL-Kontext haben, wird diese mit den Daten der
Schlüsselspeicher initialisiert.
Der folgende Codeabschnitt stammt von der
SSLRelayServer-Klasse, die auf der gleichen Maschine laufen
wird wie die Postgres-Datenbank. Damit werden alle Clientdaten
über die SSL-Verbindung nach Postgres übertragen und
umgekehrt.
SSLRelayServer-Klasse
/* initSSLServerSocket method will get the
SSLContext via its super class SSLConnection. It will then
create a SSLServerSocketFactory object that will be used
to create a SSLServerSocket. */
public void initSSLServerSocket(int localport) {
try{
//get
the SSL socket factory
SSLServerSocketFactory
ssf = (getMySSLContext()).getServerSocketFactory();
//create the ssl socket
ss
= ssf.createServerSocket(localport);
((SSLServerSocket)ss).setNeedClientAuth(true);
}
catch(Exception e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
// begin listening on SSLServerSocket and wait for incoming
client connections
public void startListen(int localport , int destport) {
System.out.println("SSLRelay server started
at " + (new Date()) + " " +
"listening
on port " + localport + " " + "relaying to
port " + destport );
while(true) {
try {
SSLSocket incoming
= (SSLSocket) ss.accept();
incoming.setSoTimeout(10*60*1000); // set 10 minutes time
out
System.out.println((new Date() ) + " connection from " +
incoming );
createHandlers(incoming,
destport); // create 2 new threads to handle the incoming
connection
}
catch(IOException e ) {
System.err.println(e);
}
}
}
Die RelayApp-Klasse, d.h. der Client-Proxy, ähnelt dem
SSLRelay-Server. Er erbt von der SSL-Verbindung zwei
Threads und benutzt diese, um die Übertragung
durchzuführen. Der Unterschied liegt darin, dass er einen
SSL-Sockel aufbaut, um mit dem entfernten
Host zu verbinden, anstatt einen SSLServer-Sockel, der auf Verbindungsaufforderungen wartet. Die letzte benötige Klasse ist der
Thread, der die eigentliche Übertragung durchführt.
Er liest einfach den Eingabestrom und schreibt diesen in einen
Ausgabestrom.
Auf dem Client benötigst du die Dateien SSLConnection.java, RelayIntoOut.java und RelayApp.java. Der Server benötigt SSLRelayServer.java, RelayIntoOut.java und SSLConnection.java. Speichere alle in einem Verzeichnis. Führe folgenden Befehl aus, um den Client-Proxy zu kompilieren.
javac RelayApp.java
Um den Server zu kompilieren, gib folgenden Befehl ein
javac SSLRelayServer.java
Mit Postgres auf deinem Server installiert, kannst du den SSLRelayServer mit sechs Kommandozeilen-Argumenten starten. Hier sind sie
java SSLRelayServer servestore trustclientcert storepass1 prikeypass0 2001 5432
Sobald der Server-proxy läuft, kannst du den Client-proxy starten. Der Client-proxy benötigt 7 Argumente, das zusätzliche ist der hostname oder die IP Adresse des Servers, zu dem du dich verbindest. Die Argumente sind:
java RelayApp clientstore trustservercert clistorepass1 cliprikeypass0 localhost 2001 5432
Sobald der SSL-Tunnel existiert, kannst du die JDBC-Anwendung starten und ganz normal Postgres öffnen. Der gesamte Übertragungsvorgang wird für die JDBC-Anwendung vollständig transparent sein.
Dieser Artikel ist bereits zu lang, ich werde daher keine weiteren Beispiele zur JDBC-Anwendung aufführen. Das Postgres-Handbuch und die Anleitung von SUN enthalten zahlreiche Beispiele zu JDBC.
Du kannst das Testen auch auf einer einzigen Maschine durchführen. Dafür bestehen zwei Möglichkeiten: entweder du änderst den Input-Port der Postgres-Datenbank oder du wechselst die Portnummer, auf die RelayApp überträgt. Ich werde den letzteren Fall anwenden, um einen einfachen Test zu demonstrieren. Zuerst schließe RelayApp mit dem kill -Befehl [strg] c. Auf die gleiche Weise kann der SSLRelayServer-Proxy geschlossen werden.
Starte wieder RelayApp mit dem folgenden Befehl, der einzige Unterschied ist die letzte Port-Nummer, sie ist jetzt 2002.
java RelayApp clientstore trustservercert clistorepass1 cliprikeypass0 localhost 2001 2002
Die beste Anwendung zum Testen ist psql selbst. Wir werden allen psql-Verkehr zu Postgres durch unseren Tunnel übertragen. Gib den folgenden Befehl ein, um psql zu für den Test zu starten
psql -h localhost -p 2002
Dieser Befehl weist psql an, mit dem Localhost am Port 2002, dem RelayApp zuhört, zu verbinden. Nach der Eingabe des Postgres-Passwort kannst du wie gewöhnlich SQL-Befehle ausführen und damit die Übertragung der SSL-Verbindung testen.
Es ist keine gute Idee, Passwörter als Befehlszeilenargumente zu benutzen, wenn deine Maschine auch von anderen benutzt wird. Es ist möglich, mit dem Befehl ps -auxww den gesamten String des Prozesses, einschliesslich der Passwörter, einzusehen. Es ist besser, die Passwörter in verschlüsselter Form in einer anderen Datei zu speichern und deine Java-Anwendung diese von dort lesen zu lassen. Als Alternative besteht die Möglichkeit, mittels Java Swing ein Dialogfeld mit Eingabeaufforderung anzulegen.
Es ist ziemlich einfach, einen SSL-Tunnel mittels Sun-JSSE zu bauen, den
Postgres benutzen kann. Wahrscheinlich kann jede andere
Anwendung, die eine sichere Verbindung benötigt, diese Art
SSL-Tunnel benutzen. Es gibt so viele Möglichkeiten, deine
Verbindungen zu verschlüsseln - starte deinen
Linux-Lieblingseditor und fang an zu kodieren! Viel Spass !