iOS: Certificate Pinning per Konfiguration

Seite 2: Pinning in der Praxis

Inhaltsverzeichnis

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()

Ü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.

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.

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)