09.09.2014

Signierten Code mit Java erstellen

Man hat's nicht leicht, mit den ganzen Updates Schritt zu halten ... Dauernd Sicherheitslücken in Windows, Flash, Java, sogar in Android findet sich die eine oder andere Lücke.
Um den Erfolg der Updates zu überprüfen, insbesondere für Flash und Java, hatte ich mir vor langer Zeit eine ganz primitive Webseite erstellt, die mir die aktuellen Versionsnummern der installierten Plugins anzeigen kann.

Seit Version 1.7 der Java Runtime werden aber lästige Warnungen ausgegeben, dass demnächst nur noch signierter Code ausgeführt wird, dass Code, der von http- und nicht https-Schema nachgeladen wird, als unsicher betrachtet wird, und überhaupt lassen die Sicherheitseinstellungen der JRE demnächst gar nix mehr zu.

Also hab ich mich mal drangesetzt und aus meiner .class-Datei eine .jar-Datei gemacht, die ich mit meinen eigenen, selbst erstellten Key signiert habe. Das ist aber natürlich nur die halbe Miete, weil man auch der JRE im Browser klarmachen muss, dass der self-signed Code auch wirklich zulässig ist. Man muss also in der policy-Datei auch noch Änderungen vornehmen. Hier ist der ganze Ablauf zusammengestellt, den ich durchlaufen musste, bis meine simple JRE-Versionsabfrage mit Firefox 31, Chrome 36 und JRE 1.8.0.11 wieder funktioniert hat.

Hier ist zunächst der Code für die Ausgabe der JRE-Versionsnummer:
import java.applet.Applet;
import java.awt.Color;
import java.awt.Label;
public class JavaVersionDisplayApplet extends Applet
{
  private Label V;
  public JavaVersionDisplayApplet() {
    this.setBackground(Color.pink);
    V = new Label(" Java Version: " + System.getProperty("java.version") + " from " + System.getProperty("java.vendor"));
    this.add(V);
  }
}
Um diesen Code zu kompilieren, muss das Java Development Kit mit dem javac-Compiler installiert sein. Da Java einigermaßen kompatibel zu alten Versionen ist, kann man auch mit einer aktuellen JRE wie 1.8 noch Code ausführen, der mit Version 1.4 kompiliert wurde. Eine Krankheit von Java ist, dass der Dateiname exakt dem Klassennamen in der Datei entsprechen muss, hier also "JavaVersionDisplayApplet.java".
javac JavaVersionDisplayApplet.java
Als nächstes müssen noch zwei Dinge vorbereitet werden: ein Schlüsselpaar für das Signieren und eine Manifest-Datei, in der der Java-Code etwas genauer beschrieben wird, also etwas poetischer gesagt: Meta-Daten.

Zunächst also das Schlüsselpaar mit dem Programm keytool erzeugen, dass bei der JRE und beim JDK mitgeliefert wird.
keytool -genkeypair -alias thomas -keystore seeling.jks -dname "CN=Thomas Seeling, OU=System Administration, O=Kleintierpraxis Berstadt, L=Berstadt, ST=Hessen, C=DE" -keypass Password1 -storepass Password2
Im weiteren verwende ich immer den Aliasnamen "thomas" für den Schlüssel. Je nach Zusammenhang ist damit entweder der private oder der öffentliche Schlüssel gemeint.

Als nächstes benötige ich noch den öffentlichen Schlüssel separat, damit ich ihn in meinem Browser importieren kann. Dieser Schritt würde entfallen, wenn ich den Schlüssel von einer öffentlichen Root CA signieren lasse oder wenn ich mir selbst eine Root CA gebaut habe und den öffentlichen Schlüssel dieser Root CA schon in meinem Browser hätte.
keytool -export -keystore seeling.jks -alias thomas -storepass Password2 -file tseeling.cer
Ich mag meine Schlüssel lieber in base64-kodiert, also wandle ich die binäre .cer-Datei noch in eine PEM-Datei um:
openssl x509 -in tseeling.cer -inform DER -out tseeling.pem -outform PEM
Weil dies "nur" ein öffentlicher Schlüssel ist, benötige ich nach dem Export aus dem Java-Keyring kein Passwort mehr.
Diese PEM-Datei kann ich nun bequem über ein Transportmittel wie Email, ftp etc. zu meinem Browser-PC übertragen und dort importieren.

Desweiteren benötige ich die schon erwähnte Manifest-Datei, die mit in die .jar-Datei gepackt und signiert werden muss. Diese Datei sieht bei mir so aus:
Permissions: sandbox
Codebase: https://admin.moeller-seeling.local/*
Application-Name: JavaVersion
Der "Application-Name" ist hier nur "Schmuck am Nachthemd" und wird für eine Infobox benötigt, in der die JRE dem Benutzer anzeigt, wer da gerade ausgeführt werden soll.

Wenn das alles zusammengestellt ist, wird nun in zwei Schritten aus der kompilierten Java-Datei und dem Manifest zunächst eine .jar-Datei erzeugt und dann diese Datei mit dem zuvor erstellten privaten Schlüssel signiert.
jar -cfm JavaVersion.jar JavaVersion.txt JavaVersionDisplayApplet.class
jarsigner -keystore /opt/jdk/jre/lib/security/seeling.jks -keypass Password1 -storepass Password2 -verbose JavaVersion.jar thomas 

 Puh, fast geschafft! Jetzt noch diese .jar-Datei in einem kleinen HTML benutzen und vom Webserver ausliefern lassen. Darauf gehe ich nur ganz kurz ein, hier ist ein Beispiel für den HTML-Code:
<table border="1">
<tr><th> The version and vendor from the JRE</th>
<td align="center">
<applet height="60" alt="Browser has Java disabled"
hspace="22" width="440"
archive="JavaVersion.jar"
code="JavaVersionDisplayApplet.class">
</applet>
</td></tr>
</table>
Soweit, so gut. Das war der erste Schritt auf dem Webserver.

Auf dem Browser-PC sind auch noch kleine Schritte nötig, damit man ein self-signed .jar ausführen darf:
man muss in die Security-Policy einen Eintrag machen, dass signierte Dateien von bestimmten Programmierern erlaubt sind, und man muss den öffentlichen Schlüssel dieses Programmierers in den Standard-Keystore der JRE importieren, die der Browser im Java-Plugin verwendet, oder alternativ einen anderen Keystore angeben, in dem dieses Zertifikat enthalten ist.

Auf Windows findet sich das unter "%ProgramFiles(x86)%\java\jre8\lib\security\java.policy" (für die Java-Version 1.8).

Zu dieser (i.a. schon vorhandenen Datei) im Klartextformat habe ich folgende Einträge hinzugeführt:

keystore "file:${java.home}/lib/security/seeling.jks", "jks";
grant signedBy "thomas" {
  permission java.security.AllPermission, signedBy "thomas";
};
Zu der Liste der vertrauenswürdigen Zertifikate habe ich meinen öffentlichen Schlüssel hinzugefügt, den ich weiter oben als PEM-Format gespeichert hatte.
keytool -importcert -noprompt -trustcacerts -alias thomas -file tseeling.pem -keystore "%ProgramFiles(x86)%\java\jre8\lib\security\cacerts" -storepass changeit
Das Passwort der Schlüsseldatei "cacerts" ist üblicherweise "changeit". Natürlich ändert es nie jemand ;)

So, nachdem also nun ganz viele kleine Gemeinheiten geschafft sind, müsste im Browser ein Applet funktionieren, das die JRE-Versionsnummer ausgibt. Wenn man auch nur einen dieser Schritte weglässt, klappt es nicht und entweder der Browser oder die JRE beschimpfen mich, dass alles ganz schröcklich unsicher ist.