arrow arrow--cut calendar callback check chevron chevron--large cross cross--large download filter kununu linkedin magnifier mail marker menu minus Flieger phone play plus quote share

Certificate Pinning, Public Key Pinning oder CA Pinning?

Ein Überblick über den Einsatz von Pinning in Android Apps.

So gut wie jede/r Entwickler*in dürfte den Begriff Certificate Pinning, Public Key Pinning oder einfach nur Pinning schon mal gehört haben. Es kann zu mehr Sicherheit in der Kommunikation beitragen, aber auch zu einem Ausfall der App führen. Dieser Artikel soll einen Überblick über Pinning geben, was es ist, wozu es dient, welche Probleme entstehen können, was für einen möglichst sicheren Einsatz im mobilen Bereich zu beachten ist und wie es für Android Apps umgesetzt werden kann. Denn, auch wenn das HTTP Public Key Pinning (HPKP) in Browsern wohl erstmal Geschichte bleibt, so wird Pinning in iOS und Android Apps noch genutzt, da bei einem Kommunikationsabbruch ein Update über einen sicheren Kanal wie den App Store oder Play Store möglich ist. Aber eins nach dem anderen.

Henrik Rampendahl

Android Developer

Das Problem mit der sicheren Kommunikation

Smartphones verbinden sich oft selbstständig mit unterschiedlichen WLANs. Speziell bei öffentlichen WLANs besteht jedoch eine erhöhte Gefahr für Man-in-the-Middle (MITM) Angriffe. Dabei täuscht der Angreifer beispielsweise einen Zugriffspunkt (Router) vor und kann dann den gesamten Datenverkehr auslesen (siehe auch u.a. Rogue Access Point, ARP-Cache-Poisoning, DNS-Spoofing). Auch wenn es schwer sein dürfte, realistische Zahlen über reale Angriffe auf öffentliche Netzwerke zu finden, so finden sich zahlreiche Anleitungen dafür im Netz, was das Gefahrenpotential untermauert und weswegen eine App, insbesondere wenn sie sensible Daten austauscht, darauf vorbereitet sein sollte. 

Mit Zertifikaten kann ein Client nun prüfen, ob er tatsächlich mit dem erwarteten Server spricht und dann eine verschlüsselte Kommunikation aufbauen. Wie das funktioniert, ist bei HTTPS im Transport Layer Security (TLS) Protokoll festgelegt.

SSL, TLS – Was war das nochmal?

Wird von HTTP auf HTTPS umgeschaltet, werden Daten nicht mehr im Klartext, sondern verschlüsselt übertragen. Dafür ist TLS (ehemals SSL) zuständig. Beim Verbindungsaufbau von Client zu Server findet ein Handshake statt, in dem sich beide auf ein Verschlüsselungsverfahren einigen. Dabei ist es wichtig, dass zumindest der Client den Server authentifiziert, um einem möglichen MITM-Angriff entgegenzuwirken. Kann der Sever kein dem Client bekanntes Zertifikat vorweisen, bricht dieser die Kommunikation ab. Bei TLS werden hierfür X.509 Zertifikate genutzt. 

Da ältere TLS und SSL Versionen bekannte Sicherheitslücken haben, empfiehlt das BSI mindestens TLS 1.2 oder besser die aktuelle Version 1.3 zu unterstützen und keine darunter. Anderenfalls werden Downgrade-Angriffe, wie etwa SSL-Stripping, möglich.

Noch mehr zu Zertifikaten und Vertrauen in der IT Security gibt’s hier im Blog

X.509 Zertifikate und die Vertrauenskette

Ein X.509 Zertifikat enthält im Wesentlichen Daten mit einer Signatur. Die Daten beinhalten einen öffentlichen Schlüssel, Angaben über den Inhaber des Schlüssels (beispielsweise eine Domain) und den Austeller des Zertifikats. Die Signatur stammt ebenfalls vom Aussteller, wird mit dem privaten Schlüssel von diesem erstellt und kann mit dem zugehörigen öffentlichen Schlüssel in seinem Zertifikat überprüft werden (siehe Abb.1). Da Root CAs den Anfang der Kette bilden, signieren sie sich selbst.

Chain of Trust
Abb.1: Chain of Trust (https://en.wikipedia.org/wiki/Chain_of_trust)

Auf Android sind standardmäßig eine große Anzahl Root Certificates von CAs vorinstalliert. Mit diesen Root CAs werden wiederum Intermediate CAs signiert, die wiederum Leaf Certificates signieren, also die öffentlichen Schlüssel von Endnutzern wie Websites oder Personen. Dadurch entsteht eine Vertrauenskette (chain of trust). Wird dem Root Certificate oder Intermediate Certificate vertraut, wird auch allen von ihnen signierten Zertifikaten vertraut. 

Das Erstellen und Verwalten von Zertifikaten wird von Public-Key-Infrastrukturen (PKIs) übernommen. Sie stellen auch Zertifikatssperrlisten (Certification Revocation Lists – CRLs) bereit, in denen nicht mehr gültige Zertifikate gelistet sind, sowie Validierungsdienste wie OCSP oder SCVP, welche die Überprüfung von Zertifikaten in Echtzeit ermöglichen. Der Trend scheint jedoch zu einer kürzeren Gültigkeit von Zertifikaten zu gehen, um möglichen Kompromittierungen entgegenzuwirken, da der Widerrufsprüfung unter anderem durch häufige Implementierungsfehler weniger vertraut wird und abgelaufene Zertifikate nicht mehr verwaltet, also in einer CRL eingetragen werden müssen. Ein Zertifikat gilt als kompromittiert, wenn der zugehörige private Schlüssel gestohlen wurde.

Vorinstalliertes Vertrauen

Grundsätzlich sind auf Betriebssystemen, wie auch auf Android, viele Root-Zertifikate von sogenannten Certificate Authorities (CAs) vorinstalliert. Inhaber von Webservices beauftragen CAs damit, ihnen ein Zertifikat für ihre Domain auszustellen. Dieses kann der Client dann überprüfen und nutzen, um eine verschlüsselte Verbindung aufzubauen. Das bedeutet jedoch auch, dass der Client implizit allen Zertifikaten vertraut, die von allen vorinstallierten CAs ausgestellt wurden. Das führt zu einer großen Ungewissheit, denn auf Android befinden sich gern >100 vorinstallierte Root CAs und es gab immer wieder Vorfälle, bei denen CAs nicht richtig geprüft haben, wem sie Zertifikate ausstellen oder schlicht gehackt wurden. Hier hilft Certificate Pinning, da es die Anzahl an vertrauenswürdigen Zertifikaten verringert und somit auch die Gefahr des Missbrauchs von Zertifikaten.

Pinnen oder nicht pinnen?

Das BSI empfiehlt Certificate Pinning für e-Health Apps, aber auch Banking Apps, Email-Clients oder Messenger und allgemein Apps, die auf online Accounts zugreifen können sind relevant. Die OWASP empfiehlt Certificate Pinning immer dann einzusetzen, wenn eine gewisse Sicherheit über die Identität des Endpunkts bestehen soll oder wenn in einer gefährlichen Umgebung kommuniziert wird (und gibt selbst die Antwort, dass dies wohl immer der Fall sein dürfte). Also sollte jede App immer Pinning einsetzen? Hier gehen die Meinungen auseinander. Ein Gegenargument ist, dass der Aufwand und die Probleme, die durch Pinning entstehen können, häufig den Nutzen übersteigen. Selbst Google rät von Pinning ab, nennt aber auch Gründe für den Eingriff in die vorinstallierten Zertifikate. Letztlich ist also wohl im Einzelfall zu prüfen, ob etwa eine bestehende Infrastruktur oder eine Risikobewertung das Pinning erforderlich macht.

Wie vertrauenswürdig sind öffentliche CAs?

Eine Website mit einem Zertifikat ist also vertrauenswürdig? Leider nicht direkt. Das Zertifikat gibt lediglich an, dass der enthaltene öffentliche Schlüssel zu dem im Zertifikat genannten Inhaber, also der Domain, gehört. Wofür diese verwendet wird, darüber sagt es erstmal nichts aus. 

Bei Android Apps wird standardmäßig darauf vertraut, dass allen vorinstallierten CAs keine Fehler unterlaufen und sie oder ihre intermediate CAs nicht (aus welchen Gründen auch immer) beispielsweise öffentliche Schlüssel von angeblichen Seiten signieren, die nicht zu diesen gehören – oder schlicht von Seiten, die Malware vertreiben. Im schlimmsten Fall werden wie im Jahr 2011 mit DigiNotar und anderen gleich mehrere CAs gehackt und mit ihren privaten Schlüsseln Zertifikate für hunderte zum Teil bekannte Websites erstellt. Bei Android 6.0 (API 23) und darunter wurde zusätzlich auch vom Nutzer hinzugefügten CAs standardmäßig vertraut, was für Social-Engineering-Angriffe verwendet werden kann, um Nutzer dazu zu verleiten, von Angreifern genutzte Zertifikate zu installieren.

Was sind private CAs?

Private CAs sind selbst betriebene CAs, die zum Beispiel innerhalb einer Firma erstellt und genutzt werden. Sie haben eigene Root-Zertifikate mit denen sie kontrollieren, an wen Zertifikate ausgestellt werden. Im Vergleich zur Nutzung öffentlicher CAs kann so die Anzahl an Zertifikaten eingeschränkt und überschaubar gehalten werden.

Die privaten Schlüssel von Root-Zertifikaten sollten immer auf einem offline Datenträger liegen, also nicht über das Internet zugänglich sein. Mit den von ihnen signierten Zwischenzertifikaten können dann Zertifikate von Endgeräten und Endpunkten signiert werden. Sollten private Schlüssel der Zwischenzertifikate in falsche Hände geraten, können sie und alle damit erstellen Zertifikate widerrufen und mit dem privaten Schlüssel des Root-Certificates neue Zwischenzertifikate erstellt werden. Alle Geräte und Programme, die dem Root-Zertifikat oder Schlüssel vertrauen, funktionieren dann noch, auch ohne Update.

Der Aufbau einer eigenen CA erfordert jedoch viel Arbeit und birgt gleichzeitig viele Gefahren durch Fehler bei der Implementierung und während des laufenden Betriebs. Ob also der eigenen Root CA oder lieber einer öffentlichen und etablierten vertraut werden soll, lässt sich allgemein schwer beantworten.

Was sollte gepinnt werden?

Ein Client kann ein Zertifikat oder den darin enthaltenen Public Key pinnen. Da es vorkommen kann, dass ein Zertifikat für den gleichen Schlüssel neu ausgestellt wird (z.B. weil die Gültigkeit abgelaufen ist), bietet das Public Key Pinning den Vorteil, dass eine App dem neu ausgestellten Zertifikat vertraut und kein Update benötigt.

Grundsätzlich kann zwischen drei Arten von Zertifikaten für das Pinning unterschieden werden:

  • Wird das Zertifikat der Root CA gepinnt, wird auch allen Intermediate CAs und deren Leaf Certificates vertraut. Es bietet somit die größte Gefahr, dass ein nicht vertrauenswürdiges Zertifikat in der Vertrauenskette ist. Bei einer eigenen privaten Root CA sollten alle ausgestellten Zertifikate hingegen bekannt sein. Wie bereits erwähnt, birgt sie aber auch Risiken. Die lange Gültigkeit von Root-Zertifikaten gewährt wiederum allgemein eine lange Nutzbarkeit.
  • Wird einer Intermediate CA vertraut, verkürzt sich die Vertrauenskette und das Risiko sinkt. Dennoch wird auch hier einer unbekannten Anzahl an Zertifikaten vertraut, die nicht nur zu den Endpunkten gehören, welche die App nutzt. Ihre Gültigkeit dürfte meist kürzer sein als von Root CAs, jedoch deutlich länger als von Leaf Certificates.
  • Wird einem Leaf Certificate vertraut, reduziert sich das Vertrauen auf das Zertifikat eines Endpunkts und bietet somit die größte Sicherheit. Diese Zertifikate haben aber meist nur eine kurze Gültigkeit. Soll ein Leaf Certificate mit einem neuen Schlüsselpaar ausgestellt werden, kann die App nicht mehr auf den Endpunkt zugreifen und benötigt ein Update.

Allgemein wird empfohlen, immer mehrere Schlüssel zu pinnen. Sollte beispielsweise ein Leaf-Zertifikat ablaufen oder kompromittiert werden, kann die Kommunikation und somit Funktionalität von Apps durch ein weiteres Zertifikat einer Intermediate CA aufrechterhalten werden, ohne direkt ein neues Update zu erfordern. 

Wie werden Zertifikate in der App verwaltet?

Im Folgenden zeige ich drei Möglichkeiten, wie Zertifikate oder der Hash eines öffentlichen Schlüssels zum Pinnen in eine App kommen können. 

  • Vorinstalliert in der App: Das statische Pinnen von Zertifikaten oder öffentlichen Schlüsseln ist das einfachste Verfahren. Vor jedem Release einer neuen App-Version sollten die genutzten Zertifikate auf ausreichend Gültigkeitsdauer und Widerruf überprüft werden. Sollten sie nicht mehr nutzbar sein, ist die App vom Netzwerk getrennt und kann bis zum nächsten Update nur noch eingeschränkt oder nicht mehr genutzt werden. Über die Network Security Configuration von Android kann jedoch auch eine Ablaufzeit gesetzt werden, nach der ein Zertifikat nicht mehr geprüft wird. So kann man beispielsweise längere Laufzeit für ältere App-Versionen ermöglichen. Andersherum kann es natürlich auch erwünscht sein, Updates nach einer Zeit zu erzwingen.
  • Trust on first use (TOFU): Beim ersten Aufruf eines Endpunkts wird das (bzw. werden die) von diesem zurückgegebene/n Zertifikat/e gepinnt. Dieses Verfahren ist hilfreich, wenn Pinning erforderlich, jedoch der Endpunkt nicht vorab bekannt ist. Die Gefahr, beim ersten Aufruf ein Zertifikat eines Angreifers zu bekommen, wird hier geringer eingeschätzt als die einer Kommunikation ohne Pinning. Es erfordert jedoch auch mehr Verwaltungslogik, beispielsweise mit dem Auslesen, Speichern und Behandeln von abgelaufenen Zertifikaten, und lässt sich nicht App-weit über die Network Security Configuration einrichten. Ein prominentes Beispiel für TOFU ist die App Signal, welche einem neuen Peer anfangs blind vertraut, jedoch den Nutzer informiert und ermöglicht, die Gegenseite bei physischem Kontakt über eine Safety Number zu verifizieren und bei Änderung dieser Nummer eine Warnung anzeigt.
  • Dynamic Pinning: Hier wird die Verwaltung von Zertifikaten einem Server überlassen. Die App benötigt zwar initial noch mindestens ein vorinstalliertes statisches Zertifikat vom Server, aber dann kann sie immer aktuelle Zertifikate abfragen. Dies bietet eine hohe Flexibilität und Sicherheit gegen den Ausfall einer App. Gleichzeitig erfordert es zunächst einen erhöhten Aufwand für die Umsetzung in Frontend und Backend.

In den meisten Fällen dürften, wenn überhaupt, vorinstallierte Zertifikate verwendet werden. Dies ist am einfachsten zu implementieren und bietet bei richtiger Umsetzung mit mehreren Zertifikaten auch genügend Sicherheit, dass auch für Nutzer, die länger kein Update machen, noch alles wie gewohnt funktioniert. Auf der anderen Seite kann es auch zum Konzept einer App, bzw. deren Herausgeber gehören, über abgelaufene Zertifikate (regelmäßige) Updates zu erzwingen. 

Was, wenn einem Zertifikat nicht vertraut wird?

Dies kann verschiedene Gründe haben. Die Gültigkeit des Zertifikats kann abgelaufen sein, es kann widerrufen oder aktualisiert worden sein oder jemand versucht tatsächlich, etwa durch DNS Spoofing, sich als Server auszugeben. Sollte der Server kein gefordertes Zertifikat nachweisen können, kann dem Server nicht oder nur blind vertraut werden. Dies führt zu einer Fehlermeldung und dem Abbruch der Verbindung. Die App wird somit eingeschränkt oder sogar unbrauchbar. Es ist wichtig, solche Fehlermeldungen abzufangen und den Nutzer zu informieren, dass der Verbindung nicht vertraut wird und die App ein Update benötigt, sollte ein Netzwerk/WLAN Wechsel nicht helfen.

Von einem aktiven Eingreifen oder dem eigenen Implementieren des Validierungsverfahrens von Zertifikaten ist allgemein abzuraten, da die Gefahr von Fehlern und somit der Reduzierung der Sicherheit hoch ist.

1. Ansatz: Network Security Configuration (NSC)

Im Folgenden fasse ich wichtige Punkte der offiziellen Android Developer Seite, die beim Einsatz der NSC beachtet werden sollten, zusammen. Die NSC wurde in Android 7 eingeführt. Sie ist nicht abwärts kompatibel, weswegen für ältere Android-Versionen eine andere Lösung benötigt wird, oder auf eine externe Library wie TrustKit-Android (unterstützt NSC bis Android API 17) zurückgegriffen werden muss.

Sofern nicht anders konfiguriert, nutzen alle Android Apps standardmäßig die auf dem System vorinstallierten CAs, wenn sie Verbindungen mit TLS etwa über HTTPS aufbauen. 
Das war jedoch nicht immer so, daher folgt hier eine kurze Änderungs-Historie der Default-Sicherheitseinstellungen auf Android:

Bis einschließlich Android 6 (API Level 23): 

  • Vorinstallierten Zertifikaten wird vertraut. 
  • Vom Nutzer selbst installierten Zertifikaten wird vertraut.
  • Klartext-Kommunikation ist erlaubt.

Ab Android 7 (API Level 24):

  • Einführung der NSC
  • Nur noch Vorinstallierten CAs wird vertraut. 

Ab Android 9 (API Level 28): 

  • Klartext-Kommunikation ist standardmäßig ausgeschaltet.

Vorsicht: Die Voreinstellungen werden durch die NSC überschrieben und können somit auch rückgängig gemacht werden. 

Wichtig: Die Einstellungen in der NSC gelten App-weit. Das schließt die WebView, bzw. den WebViewClient, mit ein.

Zwei Dinge sind grundsätzlich nötig, um mit der Konfiguration zu beginnen:

  1. Erstellen einer network_security_config.xml unter res/xml/

  2. Referenzieren dieser in der AndroidManifest.xml unter:
<application
            …
            android:networkSecurityConfig="@xml/network_security_config">

Der Verbindungsaufbau einer App kann grundlegend auf zwei Arten konfiguriert werden:

  • <domain-config>
    • Domainspezifische Einstellungen.
    • Für Verbindungen zu anderen Domains gelten weiterhin die Voreinstellungen, bzw. wenn angegeben die aus der <base-config>.
  • <base-config>
    • App-weite Einstellungen.
    • Im System enthaltenen CAs wird nicht mehr vertraut.
    • Sollen die System CAs lediglich erweitert werden (kein Pinning), müssen sie zusätzlich explizit durch ein inneres Element <certificates src="system"/> als vertrauensvoll deklariert werden.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>

   <!--  1. Beispiel: Zertifikat(e) für spezielle Domain(s)  -->
   <domain-config cleartextTrafficPermitted="false">
      <domain includeSubdomains="true">example1.com</domain>
      <trust-anchors>
         <certificates src="@raw/cert"/>
      </trust-anchors>
   </domain-config>

</network-security-config>

1. Beispiel: Zertifikat(e) für spezielle Domain(s)

Das Zertifikat cert kann im PEM oder DER Format unter res/raw/cert vorliegen. Es ist ebenfalls möglich, mehrere Zertifikate in einer PEM oder DER Datei anzulegen. Die obige Konfiguration umfasst folgende Punkte:

  • cleartextTrafficPermitted - Nur HTTPS ist erlaubt, kein Klartext über HTTP
    • Ab Android 9 (API Level 28) ist Klartext standardmäßig ausgeschaltet
  • includeSubdomains – Gilt für example1.com und alle Subdomains *.example1.com

  • Es wird nur dem/den in raw/cert enthaltenen Zertifikat(en) vertraut.
  • Für alle anderen Domains gelten die Android-Voreinstellungen.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>

   <!--  2. Beispiel: Zertifikat Schlüssel Pinning für spezielle Domain(s)  -->
   <domain-config>
      <domain includeSubdomains="true">example2.com</domain>
      <pin-set expiration="2023-07-01">
         <pin digest="SHA-256">++ABgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=</pin>
         <pin digest="SHA-256">...</pin>
      </pin-set>
   </domain-config>

</network-security-config>

2. Beispiel: Zertifikat Schlüssel für spezielle Domain(s)

Bei dem eigentlichen Public Key Pinning wird ein Base64-kodierter SHA-256 Hash des öffentlichen Schlüssels aus einem Zertifikat genutzt. Dabei kann optional ein Ablaufdatum angegeben werden, nach welchem die Prüfung nicht mehr angewendet wird.

  • includeSubdomains – Gilt für example2.com und alle Subdomains *.example2.com
  • expiration – Ab dem Datum wird das <pin-set> nicht mehr überprüft.
  • Für alle anderen Domains gelten die Android Voreinstellungen.

Der Base-64-kodierte SHA-256 Hash des öffentlichen Schlüssels im DER Format kann aus einem Zertifikat mit OpenSSL ausgelesen und erstellt werden. 
Hierzu wird über die Konsole in das Verzeichnis, in dem das Zertifikat (hier cert.pem) liegt, traversiert und die folgende Befehlskette ausgeführt:

OpenSSL x509 -in cert.pem -pubkey -noout | OpenSSL pkey -pubin -outform der | OpenSSL dgst -sha256 -binary | OpenSSL enc -base64

Liegt das Zertifikat im DER Format vor, muss OpenSSL mit -inform der darauf hingewiesen werden:

OpenSSL x509 -in cert.cer -inform der -pubkey -noout | OpenSSL pkey -pubin -outform der | OpenSSL dgst -sha256 -binary | OpenSSL enc -base64

Debugging

Falls für debug builds andere Endpunkte verwendet werden, können diese in einem separaten <debug-overrides> Element konfiguriert werden. Es kann ebenfalls ein <trust-anchors> oder ein <pin-set> Element enthalten. Im Beispiel unten wird allen im System vorinstallierten Zertifikaten vertraut.

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>

   <debug-overrides>
      <trust-anchors>
         <certificates src="system"/>
      </trust-anchors>
   </debug-overrides>
    
</network-security-config>

VORSICHT: Die NSC erlauben auch, die TLS Verschlüsselung komplett aufzuheben, indem cleartextTrafficPermitted auf true gesetzt wird. Ebenfalls ist darauf zu achten, dass die oben genannten Default- Einstellungen überschrieben werden, sollten auch Versionen vor Android 9, bzw. Android 7 unterstützt werden.

Zusammenfassung – Network Security Configuration

Die NSC bietet einen einfachen Weg, ab Android 7 (API Level 24) App weite Einstellungen für die Netzwerksicherheit vorzunehmen, ohne den Source Code zu verändern. Dies ist wohl auch der einfachste Weg, um Pinning für die WebView umzusetzen. Mit der expiration für ein pin-set wird auch eine Möglichkeit geboten, auf die Kontrolle der im pin-set angegebenen Schlüssel ab einem festen Zeitpunkt zu verzichten. Dies macht die App zwar ggf. angreifbarer, stellt jedoch auch sicher, dass eine App, die vom Nutzer selten oder nie geupdatet wird, auch nach Ablauf des Zertifikats weiter funktioniert.

Die NSC bietet jedoch auch die Möglichkeit, Voreinstellungen wie das Verbot von Klartextverbindungen oder den Gebrauch von Nutzer installierten Zertifikaten zu überschreiben, wodurch die Sicherheit von Apps eingeschränkt werden kann. Ferner lässt sie kein TOFU oder dynamisches Pinning mit Hilfe eines Pin-Servers zu.

2. Ansatz – OkHttpClient mit CertificatePinner

Der unter Android Entwicklern bekannte und oft im Zusammenhang mit Retrofit genutzte OkHttpClient kann ab Android 5 (API Level 21) verwendet werden und bietet mit dem CertificatePinner ebenfalls eine sehr einfache Möglichkeit, den SHA256 Hash eines öffentlichen Schlüssels von einem oder mehreren Zertifikaten zu pinnen.

val cp = CertificatePinner.Builder()
    .add("**.example.com",
        "sha256/jMLich8Bz+xHlz/7ydgPI0m2B+Qogm4alnhSh9v38N0=",
        "sha256/0N83v9hShnla4mgoQ+B2m0IPgdy7/zlHx+zB8hciLMj=")
    .build()
val okHttpClient = OkHttpClient.Builder().certificatePinner(cp).build()

Im obigen Beispiel wird der Domain example.com und allen ihrer Subdomains zwei Base64-kodierte SHA256 Hashs von öffentlichen Schlüsseln im DER Format aus Zertifikaten gepinnt. Die Hashs können, wir bereits oben gezeigt, über OpenSSL aus Zertifikaten erstellt werden. Mit einem Sternchen vor der Domain („*.example.com“) wird nur die erste Subdomain mit ausgewählt. Das würde hier „example.com“ und www.example.com einschließen. Soll nur die Domain ohne Subdomains gewählt werden, ist zu beachten, dass „example.com“ nicht „www.example.com“ ist und vice versa.

Zusammenfassung – OkHttpClient

Mit dem CertificatePinner lassen sich leicht Schlüssel an eine oder mehrere (Sub-)Domains pinnen. Im Gegensatz zu der Android-eigenen NSC lassen sich jedoch keine App-weiten Einstellungen vornehmen oder ganze Zertifikatsateien referenzieren, was jedoch auch nicht nötig sein muss. Allgemein ermöglicht der OkHttpClient mit dem CertificatePinner gegenüber dem App-weiten deklarativen NSC einen programmatischen Ansatz. Das bietet bei der Entwicklung viel Freiheit. Eine Zeitbegrenzung von Zertifikaten ist hier ebenso möglich wie spezifische Einstellungen für debug builds. Zudem ist durch die zur Laufzeit vorliegenden und änderbaren Schlüssel Hashs die Implementierung von TOFU oder dynamischem Pinning möglich.

Verbleibende Angriffsvektoren

Das Pinning schützt in erste Linie vor MITM-Angriffen. Bei richtiger Umsetzung ist es somit einem Angreifer nahezu unmöglich, an brauchbare Informationen aus der Kommunikation der App eines dritten heranzukommen. 
Auf dem eigenen Gerät ist es ist jedoch auch möglich, das Pinning zu umgehen. Dies ist zum Beispiel für Penetration Tests nötig, bei denen mögliche Schwachstellen von Software und API gefunden werden sollen. 

Eine Möglichkeit ist hier die Anwendung sogenannter Hooks, die zur Laufzeit einer App die entsprechenden Aufrufe zur Prüfung von Zertifikaten abfangen. Ein bekanntes Tool hierfür ist Frida. Dafür ist jedoch ein Rooten/Jailbreak des Android-Geräts nötig, bzw. das Ausführen der App in einer Umgebung, in der zur Laufzeit auf sie zugegriffen werden kann. Das liegt daran, dass Android aus Sicherheitsgründen alle Apps in einer Anwendungs-Sandbox ausführt und so voneinander isoliert, um genau solche Übergriffe zwischen Apps zu vermeiden.

Eine weitere Möglichkeit bietet das Reverse Engineering einer App. Dabei wird die App dekompiliert, um so an den Source Code zu gelangen und das Pinning auszuschalten oder die Pins gegen eigene zu tauschen. Dann wird die App neu gebaut und installiert. Der Prozess kann erschwert werden, indem der Source Code durch einen Obfuscator wie ProGuard unleserlich gemacht wird. Da jedoch die Schlüssel-Hashs eine feste Größe haben und genau wie URLs nicht obfuskiert werden dürfen, da sie sonst nicht mehr nutzbar sind, können sie immer noch gefunden werden. Eine so veränderte App muss dann jedoch noch auf andere Endgeräte kommen.

Es ist also möglich, die gepinnten Schlüssel und Endpunkte, mit denen die App kommuniziert, auszulesen und auch, als MITM die Kommunikation mitzuschneiden. Dies bedeutet jedoch einen gewissen Aufwand und ist in erster Linie nur auf dem eigenen Gerät möglich. Eine großflächige Anwendung ist sehr unwahrscheinlich, da hierfür die modifizierte App auf viele Endgeräte kommen muss oder diese gerootet sein müssen und mit einer entsprechenden Schadsoftware versehen.

Vorkehrungen gegen einen Ausfall der App

Im Folgenden werden für statisches Pinning die wichtigsten Vorkehrungen nochmal stichpunktartig festgehalten. Diese sind beispielhaft zu verstehen und es wird keine Garantie für die Vollständigkeit übernommen. Vielmehr ist es ratsam, sich individuelle Checklisten für die eigene Implementierung zu erstellen, die etwa vor jedem neuen App Release für mehr Sicherheit sorgt, dass die Funktionalität der App Version für einen ausreichenden Zeitraum gewährleistet sein sollte.

„How-to“ - Checkliste

  • Backup Keys: Immer mehrere Schlüssel Hashs pinnen!
  • Zertifikat Gültigkeitsdauer: Maximale Zeit beachten, bis ein neues Update nötig ist.
  • Dokumentation: Welche Schlüssel sind auf welchen App-Versionen (Tipp: Git).
  • Zertifikatserneuerung (auch für Intermediate und Root CA)
    • Wie oft ändert der Server sein/e Zertifikat(e)?
    • Certificate Pinning: Wann ist die nächste Erneuerung?
    • Public Key Pinning: Wie lange bleibt der öffentliche Schlüssel gleich?
  • Zwischenzertifikat: Wird es weiterhin zur Zertifizierung des Endpunkts genutzt?
  • Abstimmung mit dem Backend Team / Administration von Zertifikaten.
  • Release Testen
    •  Debug Version nutzt evt. andere oder keine Zertifikate.
    • Sind noch alle Endpunkte erreichbar?
    • Wurden Zertifikate widerrufen?

Wichtige Fragen - Checkliste

  • Was, wenn ein Zertifikat widerrufen (kompromittiert), ungültig oder erneuert wird?
  • Was, wenn das Backend die CA wechseln will?
  • Wie wird die Kommunikation zu Drittanbietern (Analytics, Logs etc.) sichergestellt?
  • Wie erfährt man, dass das Pinning und somit die Verbindung zum Server fehlschlägt?
  • Was, wenn Nutzer keine Updates installieren?
  • Was, wenn die Android-Version eines Nutzers kein Update mehr zulässt?

Fazit

Mit Certificate Pinning kann die Anzahl vertrauenswürdiger Zertifikaten enorm verringert werden, was die Sicherheit gegen MITM-Angriffe ein gutes Stück erhöhen dürfte. Die wohl einzige wirkliche Gefahr bei einfachen Pinning von Zertifikaten oder deren Schlüsseln ist die Trennung der App vom Netzwerk und somit die Notwendigkeit eines Updates. Das ist wiederum ein klarer Vorteil gegenüber HTTP Public Key Pinning im Browser, denn dieser kann seine Schlüssel Hashs nicht updaten, wenn er der Website, von der sie kommen, nicht mehr vertraut. Die App hingegen kann ein Update über den PlayStore beziehen. Ob und in welcher Form das Pinning jedoch nötig ist, muss wohl im Einzelfall entschieden werden. Auf jeden Fall sollte alles gewissenhaft getestet werden, bevor die App and Endnutzer ausgerollt wird und ein Konzept vorliegen, das bspw. beinhalten was passiert, wenn die App den Endpunkten nicht mehr vertraut.