Android SSL Problem mit Baltimore CyberTrust Root Zertifikat / oder wie man in Android Apps eigene SSL-Zertifikate verwendet

Schon wieder einen guten Monat her, dass ich hier geschrieben habe, aber wenn man sich auch mit solchen, wie den folgenden Problemen rumschlagen muss, ist es ja kein Wunder, dass man nicht mehr zum Schreiben von Blogbeiträgen kommt. :)

Folgende Problematik:

In unserer neuen Android App benutzen wir einen REST-Webservice, der über SSL angesprochen wird. Soweit für Android auch kein Problem, nur benutzen wir auf Serverseite  ein SSL-Zertifikat von Telesec welches als Root Zertifikat auf Baltimore Cybertrust verweist.

Jetzt würde man denken, na und, ist doch egal, Baltimore ist doch, neben zB. Verisign (was von Google selbst benutzt wird) eine bekannte Zertifizierungsstelle und ein von dort abgesichertes Zertifikat wird jawohl als Standard in jedem Betriebssystem enthalten sein ...

Tja, dieser gemeldete Bug beim Android-Tem aus dem Juni 2010 sagt darüber etwas ganz anderes:

http://code.google.com/p/android/issues/detail?id=9269

So also auch in unserem Fall. Unsere Android-Anwendung hängte sich immer wieder mit einer internen Android Fehlermeldung über ein ungültiges Zertifikat auf Serverseite auf.

Wenn man mit dem HandyBrowser direkt auf die Seite unseres Webservice gesurrft ist, kam natürlich auch eine SSL-Fehlermeldung, diese kann man im Browser aber wegklicken, und die entsprechende Seite trotzdem besuchen.

Innerhalb einer eigenen Anwendung gibt es diese Möglichkeit so nicht, und da die Antwort auf das Baltimore Root CA Problem von Seiten Google erst ein Jahr später kam (siehe Issue oben) und die Antwort darin bestand, doch bitte Android 2.3 zu nutzen, da erst dort das Root CA von Baltimore eingebaut wurde, war für uns, die wir auch noch Handys mit Android 2.1 unterstützen wollen, erstmal guter Rat teuer.

Zum Glück hat sich ein findiger Entwickler aus Lichtenstein diesem Problem angenommen und dafür eine Lösung entwickelt, um das Problem zu umgehen.

Hier der Blogbeitrag, der beschreibt, wie man sich ein eigenes Zertifikat in einem selbst erstellten Keystore im Ressourcen Verzeichnis seiner Android-Anwendung ablegt, und wie man dieses Zertifikat dann benutzt, um über einen eigenen WebserviceClient seinen SSL-gesicherten Webservice zu benutzen.

http://blog.antoine.li/index.php/2010/10/android-trusting-ssl-certificates/

Ich werde im Folgenden unsere Umsetzung beschreiben und mich dabei stark an den hier genannten Artikel anlehnen, da dieser auch mir bei der Impementierung als Vorlage geholfen hat.

#################

Tutorial zur Erstellung eines eigenen Keystores und
Nutzung dieses Keystores innerhalb eines eigenen Android Webservice-Clients

Vorwort:

Dieses Tutorial richtet sich weniger an Android-Einsteiger, da ein sicherer Umgang mit Android vorrausgesetzt wird. Auch in Java solte man sich relativ gut auskennen, um den Quelltext und die Hinweise auf die Nutzung der richtigen Pakete (Stichwort: "org.apache.http.conn.ssl.SSLSocketFactory" VS. "javax.net.ssl.SSLSocketFactory") zu verstehen.

Aber wenn du nach einer Websuche nach diesem speziellen Thema meinen kleinen Blog gefunden hast und auch noch bis hierhin durchgehalten hast mit dem Lesen, bist du mit Sicherheit tief in der Materie Android drin und auch Java wird dir kein Fremdwort sein. :)

Also auf gehts.
 

Schritt 1 - Zertifikate besorgen

In unserem Fall besteht die Zertifikatshierarchie aus unserem eigenen Zertifikat, darüber dem TeleSec ServerPass CA 1 und als Root Zertifikat das besagte Baltimore CyberTrust Root.

Auf dieser Seite gibt es die beiden öffentlichen Zertifikate im PEM-Format zum Download:

http://www.telesec.de/serverpass/support_rootca_akzeptanz.html#baltimoreroot

Schritt 2 - Keystore erstellen

Hier wird der BouncyCastle Provider benötigt um mit dem Java eigenen keytool Kommando (zu finden im bin-Verzeichnis eurer JRE Installation) einen eigenen Keystore zu erstellen.

Download: BouncyCastle Provider

Wie ihr gleich an den Pfaden in den folgenden Befehlen sehen werdet, bin ich hier in der Windowswelt unterwegs:

Ich lege die oben runtergeladenen Zertifikate sowie die BouncyJar in diesem Verzeichnis ab (könnt ihr natürlich beliebig anpassen):

D:\sweo\Downloads\android\ssl\demo

Als Passwort für den Keystore wähle ich hier zu Demozwecken mal "123456".

Dann gehts in die Windows Konsole und direkt ins bin-Verzeichnis meiner JRE-Installation, um dort mit einem ellenlangen Befehl den neuen Keystore zu erzeugen und die Zertifikate darin abzulegen (ich werde den Befehl hier etwas formatieren (wg. der Lesbarkeit), du kannst ihn dann natürlich einfach in einer Zeile schreiben) (Ob die Reihenfolge des Einfuegens der Zertifikate wichtig ist, weiß ich nicht, aber ich fang auch (wie Antoine Hauck in seinem Blog) mal mit dem untersten an und danach bis hoch zum Root CA): 

D:\>cd D:\sweo\Devtools\java\jre6u23\bin  

// 1. Zuerst Telesec CA in den Keystore einfuegen

D:\sweo\Devtools\java\jre6u23\bin>
keytool -importcert -v -trustcacerts 
-file "D:\sweo\Downloads\android\ssl\demo\TeleSec_ServerPass_CA_1.cer" 
-alias TelesecCA 
-keystore "D:\sweo\Downloads\android\ssl\demo\sweo_demo_keystore.bks" 
-provider org.bouncycastle.jce.provider.BouncyCastleProvider 
-providerpath "D:\sweo\Downloads\android\ssl\demo\bcprov-jdk16-145.jar" 
-storetype BKS 
-storepass 123456


// 2. Danach Baltimore CA in den Keystore einfuegen

D:\sweo\Devtools\java\jre6u23\bin>
keytool -importcert -v -trustcacerts 
-file "D:\sweo\Downloads\android\ssl\demo\BaltimoreCyberTrustRoot.cer" 
-alias BaltimoreCA 
-keystore "D:\sweo\Downloads\android\ssl\demo\sweo_demo_keystore.bks" 
-provider org.bouncycastle.jce.provider.BouncyCastleProvider 
-providerpath "D:\sweo\Downloads\android\ssl\demo\bcprov-jdk16-145.jar" 
-storetype BKS 
-storepass 123456

Zertifikat in systemweiten CA-Keystore 
bereits unter Alias <baltimorecybertrustca> vorhanden.

Moechten Sie es trotzdem zu Ihrem eigenen Keystore hinzufuegen? [Nein]:  Ja

Zertifikat wurde zu Keystore hinzugefuegt.

[D:\sweo\Downloads\android\ssl\demo\sweo_demo_keystore.bks wird gesichert.]

Zertifikat wurde zu Keystore hinzugefuegt.

[D:\sweo\Downloads\android\ssl\demo\sweo_demo_keystore.bks wird gesichert.]


// 3. Abschliessend den Keystore ueberpruefen

D:\sweo\Devtools\java\jre6u23\bin>
keytool -list 
-keystore "D:\sweo\Downloads\android\ssl\demo\sweo_demo_keystore.bks" 
-provider org.bouncycastle.jce.provider.BouncyCastleProvider 
-providerpath "D:\sweo\Downloads\android\ssl\demo\bcprov-jdk16-145.jar" 
-storetype BKS 
-storepass 123456

Keystore-Typ: BKS
Keystore-Provider: BC

Ihr Keystore enthõlt 2 Eintrõge.

TelesecCA, 21.11.2011, trustedCertEntry,
Zertifikatsfingerabdruck (MD5): E4:27:93:0D:7E:00:8F:D7:C9:64:69:5B:B7:AD:2F:93
BaltimoreCA, 21.11.2011, trustedCertEntry,
Zertifikatsfingerabdruck (MD5): AC:B6:94:A5:9C:17:E0:D7:91:52:9B:B1:97:06:A6:E4

 

Schritt 3 - Den eigenen Keystore in der eigenen Android App verwenden

 In unserem Fall benötigen wir das Baltimore SSL Zertifikat zum Aufruf unseres REST-Webservice. Um in Android einen Client zur Nutzung eines Webservice zu implementieren nutzt man normalerweise die Klasse "DefaultHttpClient", die hier erweitert wird, um den eigenen Keystore zu nutzen.

Noch wichtig ist natürlich, dass der neu  erstellte Keystore im res-Verzeichnis deiner Anwendung unter raw abgelegt wird.

Das sollte dann so aussehen:

res/raw/sweo_demo_keystore.bks

Außerdem muss die R Klasse aus deinem Package verwendet werden, um ueber R.raw.sweo_demo_keystore.bks auf deinen Keystore zuzugreifen.

Grosse Erklärungen geb ich jetzt hier nicht weiter, der Quelltext sollte für sich sprechen.
Achja, eine wichtige Sache noch vorab, nimm das SSLSocketFactory-Package von apache.org und nicht das von Java.net, da sonst der Scheme Konstruktor nicht mit https instanziert werden kann.

package de.sweo.ssldemo.libraries.webservice;

import java.io.InputStream;
import java.security.KeyStore;

import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;

import android.content.Context;
import android.util.Log;

import de.sweo.ssldemo.R;

public class SSLDemoHttpClient extends DefaultHttpClient {
  final Context context;
  
  public SSLDemoHttpClient(Context context) {
    this.context = context;
    Log.d("SSLDemoHttpClient", "Starting new Custom SSLDemoHttpClient");
  }
  
  @Override
  protected ClientConnectionManager createClientConnectionManager() {
    SchemeRegistry registry = new SchemeRegistry();
    registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    // Register for port 443 our SSLSocketFactory with our keystore
    // to the ConnectionManager
    registry.register(new Scheme("https", newSslSocketFactory(), 443));
    return new SingleClientConnManager(getParams(), registry);
  }
  
  private SSLSocketFactory newSslSocketFactory() {
    try {
      // Get an instance of the Bouncy Castle KeyStore format
      KeyStore trusted = KeyStore.getInstance("BKS");
      
      // Get the raw resource, which contains the keystore with
      // your trusted certificates (root and any intermediate certs)
      InputStream in = context.getResources().openRawResource(R.raw.sweo_demo_keystore.bks);
      
      try {
        // Initialize the keystore with the provided trusted certificates
        // Also provide the password of the keystore
        trusted.load(in, "123456".toCharArray());
        Log.d("SSLDemoHttpClient", "Load own Keystore");
      } finally {
        in.close();
      }
      // Pass the keystore to the SSLSocketFactory. The factory is responsible
      // for the verification of the server certificate.
      SSLSocketFactory sf = new SSLSocketFactory(trusted);
      // Hostname verification from certificate
      // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.htm...
      sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
      return sf;
    } catch (Exception e) {
      throw new AssertionError(e);
    }
  }
}

Soweit sogut, der Vollständigkeit halber hier noch ein Beispiel-Aufruf des neuen SSLDemoHttpClient: 

// Instantiate the custom HttpClient
DefaultHttpClient client = new SSLDemoHttpClient(getApplicationContext());
HttpGet get = new HttpGet("https://www.test.com/rest_webservice_example");
// Execute the GET call and obtain the response
HttpResponse getResponse = client.execute(get);
HttpEntity responseEntity = getResponse.getEntity();

 

Fazit

Sieht alles ziemlich kompliziert aus?

Ist es auch! Und das alles nur, weil Google nichtmal die Standard Root CAs in Android einbauen kann ...

Naja, ab Android 2.3 ist Baltimore ja nun drin, aber viele Handys haben noch kein 2.3 drauf, was sich aber hoffentlich bald ändern wird.

Aber auch ohne das Baltimore Root CA Problem wird es immer wieder die Anforderung geben ein eigenes SSL-Zertifikat in seiner Android App zu verwenden und das Vorgehen ist dann natürlich immer das gleiche.

Vielleicht spart dieser Artikel dir Zeit beim Entwickeln deiner App, mir jedenfalls hat der Artikel und natürlich auch der Quelltext von Antoine Hauck sehr weitergeholfen.

Apple vs. Android

Neben der Verteilung unterschiedlichster Versionen auf den Android Handys, ist es halt auch ein Problem, dass jeder Handy Hersteller seine custimize Version von Android auf seinen Geräten drauf hat.

Und bis diese Hersteller dann ihr System auf die neue Android Version hochgezogen und auf alle Geräte ausgeliefert haben, dauert das leider. (Was Google natürlich nicht davon befreit von Anfang an zumindest alle grossen SSL Root CA's mit einzubinden).

Aber wenn man dann liest, dass zB. Apple und Microsoft das Baltimore Cybertrust Root CA verwenden, dann weiß man doch schon warum Google sich so lange Zeit läßt dieses Root CA in Android einzubauen. ;)
Ein Schelm wer Absicht dahinter vermutet ...

Ich habe inzwischen mit verschiedensten mobilen Betriebssystemen gearbeitet und auch für diese Systeme entwickelt und habe mich nicht ohne Grund privat für ein IPad als Tablet entschieden.
 

Android-Verteilung Oktober 2011

Um den Sinn deiner/eurer Mühe/n zu unterstreichen, hier ein Link zu einer aktuellen Statistik:
http://de.statista.com/statistik/daten/studie/180113/umfrage/anteil-der-...

Hat mich ehrlich gesagt überrascht, wie wenig verbreitet die aktuellen Versionen sind. Liegt sicherlich mit an Google's Quellcode-Politik.

Wir haben uns 2011 zu Testzwecken ein einfaches Android-Tablet gekauft (2.1). Und ich muss sagen, es nervt tierisch, dass ein großer Teil der für mich interessanten Anwendungen entweder für unser Gerät nicht zur Verfügung gestellt wird (Version, Auflösung) oder nicht funktioniert (Hardware).

An dieser Stelle frage ich mich dann, ob Apple nicht doch den richtigen Weg geht, einen Teil der Freiheit einer hohen Kompatibilität und Sicherheit zu opfern.