iOS: Certificate Pinning per Konfiguration
App-Entwickler können die für TLS-Verbindungen erlaubten Schlüssel seit iOS 14 per Konfiguration einschränken.
- Andreas Kurtz
Apps schauen beim Aufbau verschlüsselter Verbindungen häufig ganz genau nach, ob sie mit dem richtigen Server sprechen – ob sich ein Server also auch tatsächlich mit dem erwarteten TLS-Zertifikat ausweist. Bislang mussten App-Entwickler für ein solches Certificate Pinning stets eigenen Code schreiben – mit iOS 14 geht das nun deutlich einfacher. Über diese bislang weitestgehend unbekannte Möglichkeit zum Certificate Pinning per Konfiguration informiert Apple die Entwickler-Gemeinde per News-Meldung; das Folgende gibt einen schnellen Einstieg in Pinning im Allgemeinen und die konkrete Umsetzung unter iOS.
Das leistet Pinning
Beim Aufbau einer verschlüsselten Verbindung über das Transport Layer Security (TLS) Protokoll authentifiziert sich der Server gegenüber der App mit einem Zertifikat. Der in dem Zertifikat enthaltene öffentliche Schlüssel des Servers wird verwendet, um sich auf ein gemeinsames Geheimnis für die Verschlüsselung zu verständigen. Damit die App weiß, dass sie auch wirklich mit dem angedachten Backend spricht – und nicht etwa mit einem Machine-in-the-Middle-Angreifer – muss sie erst die Vertrauenswürdigkeit des Zertifikats prüfen. Dazu ist jedes Zertifikat von einer Zertifizierungsstelle (Certificate Authority, CA) signiert.
Das Betriebssystem prüft dann bei jedem Verbindungsaufbau, dass unter anderem der in dem Zertifikat angegebene Hostname dem des angefragten Systems entspricht und dass das vom Server präsentierte Zertifikat von einer vertrauenswürdigen Zertifizierungsstelle (Trusted CA) ausgestellt und signiert wurde. Für diesen letzten Schritt hat iOS einen systemweiten Zertifikatsspeicher (Trust Store), in dem die Root-Zertifikate zahlreicher öffentlicher CAs vorinstalliert sind.
Schaut man sich die Liste der in iOS 14 vorinstallierten Root-Zertifikate aber genauer an, könnte einem angst und bange werden. Der Trust Store enthält mehr als 150 als vertrauenswürdig eingestufte Root-Zertifikate von teils mehr oder weniger bekannten, auch regierungsnahen, Zertifizierungsstellen vieler Länder.
Im Hinblick auf die zahlreichen Vorfälle in der Vergangenheit, von der Kompromittierung ganzer CAs, bis hin zum Ausstellen gefälschter Zertifikate, ist ein gewisses Misstrauen durchaus angebracht. Um Missbrauch möglichst zu verhindern, wurden in den vergangenen Jahren Kontrollinstrumente wie Certificate Transparency (CT) eingeführt. Trotzdem stellt sich die Frage, ob man tatsächlich allen CAs im Trust Store uneingeschränkt vertrauen möchte.
Weiter besteht das Risiko, dass Benutzer per Social Engineering sogenannte Konfigurationsprofile oder Mobile-Device-Management-Profile untergejubelt bekommen und sich so unbemerkt zusätzliche CAs einfangen. Apple hat die Hürden für solche Angriffe zwar erhöht, aber ein gewisses Risiko bleibt bestehen.
App-eigene Validierung
Aus diesen Gründen gehen immer mehr Apps dazu über, sich nicht auf die Validierung des Betriebssystems zu verlassen, sondern ihre Zertifikate selbst unter die Lupe zu nehmen. Bei diesem sogenannten Certificate Pinning wird das erwartete TLS-Zertifikat meist schon während der Entwicklung in der App hinterlegt und später bei jedem Verbindungsaufbau geprüft, ob das Backend auch das erwartete Zertifikat präsentiert.
Auch Standards wie der OWASP Mobile Application Security Verification Standard (MASVS) fordern solche App-eigenen Zertifikatsvalidierungen in der Verifizierungsstufe L2 fĂĽr alle Apps, die mit sensitiven Daten hantieren, wie beispielsweise Gesundheits- oder Finanz-Apps.
In der Praxis haben sich verschiedene Pinning-Verfahren etabliert, bei denen entweder das gesamte Zertifikat in der App hinterlegt wird oder nur der im Zertifikat enthaltene öffentliche Schlüssel (Public Key Pinning). Bislang mussten App-Entwickler dafür eigenen Code schreiben oder fertige Open-Source-Lösungen wie TrustKit in ihre App integrieren. Mit iOS 14 geht das nun deutlich einfacher, ohne den Code der App anpassen zu müssen.
Transport Security fĂĽr Apps
Auf Apples Plattformen kümmert sich schon seit Längerem ein Mechanismus namens App Transport Security (ATS) darum, dass für alle Apps HTTPS erzwungen wird und Mindestanforderungen an eine sichere TLS-Verbindung eingehalten werden. Dieser Mechanismus wurde mit iOS 14 nun erweitert und bietet Apps fortan die Möglichkeit, die erwarteten Schlüssel ihrer Kommunikationspartner einfach in der zentralen Info.plist Konfigurationsdatei zu hinterlegen.
Für alle Verbindungen, die eine App dann über die Standard API, konkret über das URL Loading System initiiert, gleicht iOS die jeweils vom Backend gemeldeten Schlüssel mit den in der Konfiguration hinterlegten Schlüsseln ab. Stimmen die Schlüssel nicht überein, wird der Verbindungsaufbau abgebrochen und mit einer Fehlermeldung beantwortet. Auf diese Weise können Angriffe auf die Kommunikation abgewehrt werden, selbst wenn ein Angreifer im Besitz eines gefälschten Zertifikats von einer vertrauenswürdigen CA wäre.
Wichtig ist, dass dieser Schutzmechanismus tatsächlich nur für Verbindungen über das URL Loading System greift. Wenn Entwickler nicht die Standard-APIs verwenden, sondern über Low-Level-Methoden wie das Network Framework, CFNetwork oder Sockets eigene Lösungen bauen, greifen die Einschränkungen nicht.
Für Android Apps steht übrigens ein solcher Mechanismus über die Network Security Configuration schon seit Längerem zur Verfügung. Seit Android 7.0 lassen sich hier Sicherheitseinstellungen und Zertifikate beziehungsweise Schlüssel auf ähnliche Weise per Konfiguration festlegen.
Pinning-Varianten: Leaf, Intermediate oder Root
Certificate oder Public Key Pinning kann auf verschiedenen Ebenen ansetzen. Eine App könnte direkt das End-Zertifikat (Leaf Certificate) beziehungsweise den Schlüssel des Servers prüfen und dann nur noch eine Verbindung aufbauen, wenn vom Server exakt der vorkonfigurierte Schlüssel zurückgemeldet wird. Das kann aber ganz schnell zu Problemen führen, wenn sich der Schlüssel des Servers nämlich mal ändern sollte, weil er beispielsweise zurückgerufen oder ausgetauscht wurde. Dann kommt die App nicht mehr zu ihrem Backend.
Deutlich weniger fehleranfällig ist es daher, anstatt einzelner Server-Zertifikate nur die Zertifizierungsstelle vorzuschreiben. Dies geschieht, indem man übergeordnete Zertifikate innerhalb der Zertifikatskette, wie bestimmte Intermediate- oder Root-Zertifikate einer CA, festnagelt. Ein solcher Ansatz zeigt sich robuster gegenüber Zertifikatswechseln und stellt trotzdem sicher, dass nur Zertifikate akzeptiert werden, die von der vorgeschriebenen Zertifizierungsstelle ausgestellt wurden. So erreicht man immerhin, nur der einen CA vertrauen zu müssen, nicht aber den Hundert anderen im Trust Store. Für Enterprise-Apps ließe sich hier auch ohne weiteres eine unternehmensinterne CA erzwingen.
Apropos Firmen-CA: In Firmen-Netzen, in denen TLS-gesicherte Verbindungen inspiziert werden, indem ein Monitoring-Gerät sich aktiv in die Verbindung einklinkt, wird es zu Problemen kommen. Auch wenn dessen CA scheinbar gültige Zertifikate ausstellt, erkennt das Pinning das untergeschobene MITM-Zertifikat und bricht die Verbindung ab.
Pinning in der Praxis
Um eine Pinning-Konfiguration zu erzeugen, wird der öffentliche Schlüssel benötigt, den man anheften möchte. Im einfachsten Fall lässt sich ein Schlüssel aus dem zugehörigen X.509 Zertifikat über die ASN.1 Subject Public Key Info Struktur auslesen. Liegt der Schlüssel dann in DER Kodierung vor, wird darüber ein SHA256-Hashwert berechnet und zur Weiterverwendung Base64 kodiert.
Ein Beispiel: Im Fall der Domain heise.de signiert Let’s Encrypt die Zertifikate. Ein sicherheitsbewusster Entwickler möchte deshalb also sicherstellen, dass eine App nur noch dann eine Verbindung zu heise.de aufbaut, wenn das vom Server präsentierte Zertifikat tatsächlich von Let’s Encrypt ausgestellt wurde.
Die Zertifikatsübersichtsseite von Let’s Encrypt erklärt, dass alle Server-Zertifikate über das "Let’s Encrypt R3" Intermediate-Zertifikat ausgestellt werden. Dieses Intermediate-Zertifikat beziehungsweise den darin enthaltenen Schlüssel kann man also festzurren. Dazu lädt man das R3-Zertifikat im PEM-Format herunter, extrahiert daraus dann den öffentlichen Schlüssel und berechnet den SHA-256-Hash-Wert davon. Das Kommandozeilenwerkzeug openssl hilft dabei:
$ curl -s -O https://letsencrypt.org/certs/lets-encrypt-r3.pem
$ cat lets-encrypt-r3.pem | \
openssl x509 -inform pem -noout -outform pem -pubkey | \
openssl pkey -pubin -inform pem -outform der | \
openssl dgst -sha256 -binary | openssl enc -base64
jQJTbIh0grw0/1TkHSumWb+Fs0Ggogr621gT3PvPKG0=
Zukünftig sollen Zertifikate laut Let’s Encrypt auch über ein neues ECDSA Intermediate-Zertifikat mit der Bezeichnung "Let’s Encrypt E1" ausgestellt werden. Das liefert analog den SHA256-Hash J2/oqMTsdhFWW/n85tys6b4yDBtb6idZayIEBx7QTxA=. Der soll gleich mit in die Liste der erlaubten CA-Schlüssel.
Die so ermittelten Hash-Werte im Base64-Format kann man direkt in eine Pinning-Konfiguration übernehmen. Das geschieht innerhalb der Info.plist Konfigurationsdatei der App über einen NSAppTransportSecurity-Eintrag mit der Option NSPinnedDomains. Die Option NSPinnedCAIdentities legt schließlich fest, dass in der vom Server zurückgemeldeten Zertifikatskette für die Domain heise.de einer der beiden Let’s-Encrypt-Schlüssel enthalten sein muss. Daraus ergibt sich dann folgende Pinning-Konfiguration:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>heise.de</key>
<dict>
<key>NSPinnedCAIdentities</key>
<array>
<dict>
<!-- Let�s Encrypt R3 -->
<key>SPKI-SHA256-BASE64</key>
<string>jQJTbIh0grw0/1TkHSumWb+Fs0Ggogr621gT3PvPKG0=</string>
</dict>
<dict>
<!-- Let's Encrypt E1 -->
<key>SPKI-SHA256-BASE64</key>
<string>J2/oqMTsdhFWW/n85tys6b4yDBtb6idZayIEBx7QTxA=</string>
</dict>
</array>
</dict>
</dict>
</dict>
Eine so gesicherte App würde zu heise.de nur noch dann eine Verbindung aufbauen, wenn das vom Server gemeldete Zertifikat von Let’s Encrypt ausgestellt wurde. Etwaige Zertifikats- oder Schlüsselwechsel würden die Funktionsfähigkeit der App aber nicht beeinträchtigen, solange Zertifikate weiterhin von Let’s Encrypt ausgestellt werden und seitens Let’s Encrypt keine größeren Umstellungen anstehen.
Im Fall einer fehlgeschlagenen Zertifikatsvalidierung meldet iOS den Umstand per SecureConnectionFailed Error an die App, die App würde aber standardmäßig keinen Warnhinweis anzeigen. Daher ist es wichtig, dass Entwickler in ihrer App selbst eine Fehlerbehandlung durchführen, um auf etwaige Probleme beim Herstellen einer sicheren Verbindung zu reagieren. Eine Beispiel-Fehlerbehandlung könnte so aussehen:
let task = urlSession.dataTask(with: url) { data, _, error in
switch error {
case .some(let error as NSError) where error.code ==
NSURLErrorNotConnectedToInternet:
showOfflineView()
case .some(let error as NSError) where error.code ==
NSURLErrorSecureConnectionFailed:
showSecureConnectionFailedView()
case .some:
showGenericErrorView()
case .none:
renderContent(from: data)
}
}
task.resume()
Vorsicht bei Subdomains
Über den Konfigurationsschalter NSIncludesSubdomains könnte ein einmal angehefteter Schlüssel auch für alle Subdomains einer Domain erzwungen werden. Das ist allerdings mit Vorsicht zu genießen: heise.de verwendet zwar Zertifikate von Let’s Encrypt. HTTP-Anfragen an https://heise.de beantwortet der Server aber mit einem Redirect auf https://www.heise.de. Und diese Domain verwendet keine Let’s Encrypt Zertifikate mehr, sondern setzt auf die Sectigo CA.
Schriebe man also in der Pinning-Konfiguration einer App nun Let’s Encrypt via NSIncludeSubdomains auch für alle Subdomains fest, würde diese den Verbindungsaufbau nach der Weiterleitung auf www.heise.de abbrechen. Hier muss der Entwickler also ganz genau prüfen, mit welchen Systemen eine App kommuniziert und ob alle Systeme tatsächlich mit Zertifikaten der gleichen CA ausgestattet sind. Andernfalls sollte man besser für jede Domain die Schlüssel separat konfigurieren.
Risiken und Nebenwirkungen
Wie eingangs erwähnt, ist Pinning nicht ohne Risiko. Wenn sich Schlüssel am Server ändern, erreicht die App unweigerlich ihr Backend nicht mehr. Das wird insbesondere dann zum Problem, wenn Schlüssel oder Zertifikate regelmäßig rotieren. ATS bietet über die Option NSPinnedLeafIdentities zwar auch die Möglichkeit, direkt Server-Schlüssel (Leaf Identities) festzunageln, aber das sollte man besser nur tun, wenn man sich seiner Schlüsselverwaltung extrem sicher ist (und dabei vorsichtshalber auch gleich einen oder besser zwei Reserveschlüssel einbezieht).
Auch bei gemanagten Cloud-Lösungen ist Vorsicht geboten, da man beispielsweise bei gebrauchsfertigen API-Gateways die Schlüssel meist nicht selbst im Griff hat. So hat Microsoft beispielsweise erst im letzten Jahr die Root CA Zertifikate für diverse Azure Dienste ausgetauscht und Entwickler aufgefordert, mögliche Pinning-Routinen zeitnah anzupassen.
Im Browser-Umfeld hat sich das als HTTPS Public Key Pinning (HPKP) bekannte Verfahren daher nicht durchgesetzt. Zu groĂź war die Gefahr fĂĽr fehlerhafte Konfigurationen, durch die Webseitenbetreiber ihre Nutzer fast unwiderruflich ausgesperrt haben. Mit Chrome 67 hat sich Google dann vollends von HPKP verabschiedet und somit Zertifikats-Pinning fĂĽr Webseiten faktisch beendet.
Bei Apps ist Pinning trotzdem weiter auf dem Vormarsch, denn die Risiken sind hier etwas besser im Zaum zu halten: Zum einen kontrolliert ein Anbieter mit App und Backend häufig beide Endpunkte und zum anderen, und das ist ein entscheidender Vorteil, existiert für das Pinning in Apps ein unabhängiger Recovery-Kanal. Bei HPKP wurden die Pins per HTTP Response Header ausgegeben und im Browser gespeichert. Eine Fehlkonfiguration oder ein Zertifikatswechsel hatten zur Folge, dass der einzig existierende Update-Pfad zwischen Browser und Website abgeschnitten war und Browser dann nicht mehr mit den neuen Zertifikaten versorgt werden konnten. Für Apps steht über den App Store allerdings ein unabhängiger Update-Kanal zur Verfügung, über den im Notfall zumindest per App Update noch eine neue Konfiguration verteilt werden kann.
Backup-Strategie einplanen
Wenn man sich dazu entscheidet ein Pinning umzusetzen, sollte man sich also vorab Gedanken über eine Langzeitstrategie machen und die Vor- und Nachteile der beiden Pinning-Varianten (Leaf versus CA) genau abwägen. Dabei sollte man insbesondere durchspielen, wie man auf geplante, aber auch ungeplante Ereignisse reagiert. Das kann das regelmäßige Wechseln eines Server-Zertifikats sein, das Zurückrufen eines Zertifikats, oder ein Wechsel der Zertifizierungsstelle. Grundsätzlich empfehlenswert ist es immer, mehrere Schlüssel anzuheften, vor allem einen Notfall-Schlüssel, der für eine Backup-Verbindung zum Server kurzfristig aktiviert werden kann.
Wenn alle Stricke reiĂźen, hilft dann als letzter Ausweg nur noch ein Update der App ĂĽber den Store, um so eine neue Pinning-Konfiguration auszurollen. Auch fĂĽr diesen Fall sollte noch ein "ungepinnter" RĂĽckkanal zum Benutzer existieren, um auf das im Store verfĂĽgbare Update hinzuweisen.
Einen Nachteil hat das Pinning per Konfiguration gegenüber eigenentwickelten Implementierungen dann doch: Die Schlüsselkonfiguration via ATS kann nur per App Update über den Store geändert werden. Ein Austausch der Schlüssel beziehungsweise Zertifikate zur Laufzeit ist nicht vorgesehen. Falls das benötigt wird, muss ein Entwickler dann doch wieder selbst Hand anlegen.
Aber Vorsicht: Eine eigene Zertifikatsvalidierung zu implementieren ist im Detail ein komplexes Unterfangen und fehlerträchtig. Damit der Schuss am Ende nicht nach hinten losgeht, sollte man gemäß dem Mantra "Don't Roll Your Own Security" nach Möglichkeit stets auf die neuen Pinning-Funktionen der App Transport Security zurückzugreifen. Apple hat hier mit iOS 14 endlich ein lang ersehntes Feature zum Pinning per Konfiguration nachgeliefert.
(ju)