Java SSL-Konfiguration mit Keystores

Hier wird das Erstellen eines lokalen Keystores mit self signed certificate für einen Tomcat und Java-Clients beschrieben. Die Inhalte sind größtenteils aus einer externen Quelle übernommen und darüber hinaus durch weitere Details ergänzt.

Im Rahmen dieses Artikels werden diverse Dateien erzeugt, die für die Erzeugung eines Java-Keystores mit selbst-signierten Zertifikaten verwendet werden. Mit einem durch eine offizielle Certificate Authority (CA) signierten Zertifikat sind einige Schritte nicht notwendig.

Erstellen des lokalen Keystores für den Tomcat

  1. Erstellen eines CA-Zertifikats, mit dessen Hilfe das weiter unten selbst erstellte Zertifikat signiert werden kann. An den entsprechenden Stellen ist ein eigenes Passwort zu vergeben. Hier wurde das Default Keystore Passwort „changeit“ verwendet, sollte in einer Produktionsumgebung aber geändert werden. 1. Private Key für den Server: openssl genrsa -des3 -out server.key 1024
  2. Generieren eines Certificate Signing Request (CSR): openssl req -new -key server.key -out server.csr
  3. Kennwort vom Server-Key entfernen: cp server.key server.key.org openssl rsa -in server.key.org -out server.key
  4. Zertifikat mit Server-Key erzeugen openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
  5. Erstellen eines Zertifikats für den Tomcat 1. Mit Hilfe des Keytools aus dem JDK wird ein Zertifikat für den Tomcat erzeugt. Das Keytool ermittelt interaktiv die benötigten Attribute des Zertifikats. keytool -genkey -alias tomcat -keyalg RSA -keystore tomcat.jks
  6. Generieren eines CSR: keytool -keystore tomcat.jks -alias tomcat -certreq -file tomcat.csr
  7. Eine „eindeutige“ Zahl generieren: echo 02 > serial.txt
  8. Den Tomcat CSR mit dem Server-Key aus Schritt 1. signieren: openssl x509 -CA server.crt -CAkey server.key -CAserial serial.txt -req -in tomcat.csr -out tomcat.cer -days 365
  9. Das CA Zertifikat in den Keystore importieren: keytool -import -alias serverCA -file server.crt -keystore tomcat.jks
  10. Das Tomcat Zertifikat in den Keystore importieren: keytool -import -alias tomcat -file tomcat.cer -keystore tomcat.jks

Konfigurieren des Tomcats mit SSL-Connector
Der Connector zur Verwendung von SSL ist normalerweise in der Tomcat server.xml schon enthalten, aber per Default  auskommentiert. Am besten sucht man nach einem Eintrag wie:

<Connector port="8443" protocol="HTTP/1.1"

Der Connector wird einkommentiert. Dann muss der oben erstellte Keystore im Connector konfiguriert werden. Ein vollständiger Eintrag sieht aus wie folgt:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" keystoreFile="/path/to/my/certs/tomcat.jks" keystorePass="changeit" clientAuth="false" sslProtocol="TLS"/>  

Konfiguration des Clients
Beim SSL-Handshake übermittelt der Server (hier der Tomcat) sein eigenes Zertifikat und das passende CA-Zertifikat, mit dem signiert wurde. Der Client versucht nun, das CA-Zertifikat in seiner eigenen Liste von vertrauenswürdigen Zertifikaten zu finden. Dem Client muss also das CA-Zertifikat des Servers bekannt sein.
Eine weitere Validierung prüft, ob der Server Nachrichten entschlüsseln kann, die der Client mit dem Public Key des Servers verschlüsselt. Dazu muss der Client im Besitz des Public Keys des Servers sein.

Die oben beschriebene Einbindung des Zertifikats ist schon testbar, indem man jetzt den Tomcat startet und die URL https://localhost:8443 aufruft. Der Browser sollte eine Meldung zeigen, die auf ein nicht vertrauenswürdiges Zertifikat hinweist.
Das lässt sich korrigieren, indem das oben erstellte Server-Zertifikat server.crt im Browser als vertrauenswürdiges Root Zertifikat importiert wird.

So wie der Browser eine entsprechende Warnung zeigt, wird auch ein Java-Client eine Exception werfen, sobald man eine SSL-Verbindung zum Tomcat aufbauen möchte, aber die notwendigen Zertifikate fehlen. Die Fehlermeldungen enthalten meist etwas wie PKIX path validation failed. Um dem Client die passenden Zertifikate bekanntzugeben gibt es verschiedene Möglichkeiten.

Für die Zertifikate der allgemein bekannten CAs gibt es im JDK bereits unter

$java.home/lib/security/cacerts

einen Keystore, der einige CA-Zertifikate enthält und der vom JDK automatisch berücksichtigt wird. Da dieser Keystore sehr zentral ist und ggf. wegen fehlender Berechtigungen nicht geändert werden kann, gibt es auch die Möglichkeit den Truststore per Parameter zu deklarieren (oder die Properties zur Laufzeit zu setzen):

java -Djavax.net.ssl.trustStore=clientKeystore.jks -Djavax.net.ssl.trustStorePassword=changeit com.example.App  

Um einen vorhandenen Keystore mit den notwendigen Zertifikaten zu ergänzen sind zwei Imports notwendig:

  1. Import der Server CA in den Java-Keystore clientKeystore.jks: keytool -import -alias serverCA -file server.crt -keystore clientKeystore.jks
  2. Import des Tomcat Zertifikats in den Java-Keystore: keytool -import -alias tomcat -file tomcat.cer -keystore clientKeystore.jks

Erweiterte Konfigurationsmöglichkeiten von Client und Server

Bis hierher sollte alles Notwendige konfiguriert sein, um den SSL-Handshake erfolgreich durchführen zu können. Um einige Spezialfälle abzudecken wird im Folgenden auf weitere Möglichkeiten eingegangen, sowohl den Client als auch den Server mit gültigen Zertifikaten zu versorgen oder gar die Validierungen aufzuweichen.

Extrahieren des Client-Zertifikats aus einem Server-Zertifikat
Falls das Server-Zertifikat noch nicht lokal vorliegt, kann man es häufig von einer HTTPS-URL oder aus einem Java-Keystore erhalten.

Das Exportieren aus einem Java-Keystore erledigt folgendes Kommando:

keytool -export -alias tomcat -keystore tomcat.jks -rfc -file tomcat.cert  

Das Exportieren aus einer HTTPS-URL gelingt z.B. mit der InstallCert-Klasse. Das Tool extrahiert die von einem Host übertragenen Zertifikate und speichert sie in einem Java-Keystore namens jssecacerts. In der Regel sind in diesem Keystore mehr als nur ein Zertifikat enthalten. Man kann nun entweder das gewünschte Zertifikat exportieren und in einen eigenen Keystore importieren oder den Keystore mit Hilfe der o.g. VM-Parameter verwenden.

Verwendung eines speziellen Trustmanagers auf dem Client
Eine weitere Alternative einen Client zu überreden, das Server-Zertifikat zu akzeptieren ist die Verwendung eines eigenen Trustmanagers. Man findet verschiedene Beispiele im Internet, die das SSL-Konzept durch Deaktivierung aller Validierungen untergraben. Eine der saubersten Lösungen findet man im Projekt self-signed-cert-trust-manager.

Darüber hinaus kann man auch noch die Prüfung auf URL-Spoofing deaktivieren, indem man einen eigenen HostnameVerifier anmeldet. Siehe dazu die JDK-Doku oder unter diversen Blogs.

Verwendung eines speziellen Trustmanagers auf dem Server
Für den Tomcat gibt es ab Version 6.0.33 für den Connector eine neue Option

acceptAllCerts="false|true"

die dafür sorgt, dass alle Zertifikate akzeptiert werden. Technisch hat die Option die gleiche Auswirkung wie eben für den Client beschrieben. Ähnlich funktioniert das Anmelden eines Connection-Listeners, der selbst wieder an einen speziellen Trustmanager deligiert.

Falls man keinen Tomcat verwendet, helfen wieder VM-Parameter bei der Konfiguration von Keystores. Eine gute Beschreibung findet man auf Stackoverflow.