webentwicklung-frage-antwort-db.com.de

Wie kann ich SSL-Zertifikat-Pinning bei der Verwendung von React Native implementieren?

Ich muss SSL Certificate Pinning in meiner realen Anwendung implementieren.

Ich weiß sehr wenig über SSL/TLS, geschweige denn das Feststecken ... Ich bin auch kein nativer Entwickler von Mobilgeräten, obwohl ich Java kenne und Objective-C in diesem Projekt genug gelernt habe, um herumzukommen.

Ich begann zu suchen, wie ich diese Aufgabe ausführen kann.

Implementiert React Native dies nicht bereits?

Nein, meine erste Suche führte zu diesem Vorschlag , der seit dem 2. August 2016 keine Aktivität erhalten hat.

Daraus habe ich gelernt, dass Reaktives OkHttp verwendet, das Pinning unterstützt, aber ich könnte es nicht von Javascript ableiten, was nicht wirklich eine Anforderung ist, sondern ein Plus.

Implementiere es in Javascript.

Während reakt so aussieht, als würde es die nodejs-Laufzeitumgebung verwenden, ähnelt es eher einem Browser als einem Knoten, was bedeutet, dass nicht alle systemeigenen Module unterstützt werden, insbesondere das https-Modul, für das ich das Zertifikat-Pinning nach this article implementiert hatte. So konnte es nicht nativ reagieren.

Ich habe versucht, rn-nodeify zu verwenden, aber die Module funktionierten nicht. Dies ist seit RN 0,33 bis RN 0,35 der Fall, an dem ich gerade bin.

Implementieren Sie das Phonegap-Plugin

Ich dachte daran, ein phongape-plugin zu verwenden, da ich jedoch auf Bibliotheken angewiesen bin, die reagieren müssen 0.32+, kann ich reag-native-cordova-plugin nicht verwenden.

Mach es einfach nativ

Obwohl ich kein nativer App-Entwickler bin, kann ich immer einen Riss nehmen, nur eine Frage der Zeit.

Android verfügt über ein Zertifikat, das das Festhalten von Zertifikaten ermöglicht

Ich habe erfahren, dass Android SSL Pinning unterstützt, jedoch nicht erfolgreich war, da es so aussieht, als ob dieser Ansatz vor Android 7 nicht funktioniert. Außerdem funktioniert er nur für Android.

Die Quintessenz

Ich habe mehrere Richtungen ausgeschöpft und werde mich weiter mit der nativen Implementierung beschäftigen, vielleicht herausfinden, wie man OkHttp und RNNetworking konfiguriert, und dann vielleicht zurück zu reaktiver Natur.

Gibt es bereits Implementierungen oder Anleitungen für IOS und Android?

29
Amr Draz

Nachdem ich das aktuelle Spektrum der verfügbaren Optionen von Javascript ausgeschöpft hatte, entschied ich mich, das nominale Festlegen von Zertifikaten einfach einzuführen. Jetzt scheint alles so einfach zu sein, dass ich fertig bin.

Springen Sie zu den Überschriften mit dem Titel Android Solution und IOS Lösung, wenn Sie den Vorgang des Erreichens der Lösung nicht durchlesen möchten.

Android

Ich folgte Kudos Empfehlung Ich überlegte, das Pinning mit okhttp3 zu implementieren.

client = new OkHttpClient.Builder()
        .certificatePinner(new CertificatePinner.Builder()
            .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
            .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
            .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
            .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
            .build())
        .build();

Zuerst lernte ich, wie man ein natives Android bridge with rea native) ein Toastmodul erstellt. Ich habe es dann um eine Methode zum Senden einer einfachen Anfrage erweitert

@ReactMethod
public void showURL(String url, int duration) {
    try {
        Request request = new Request.Builder()
        .url(url)
        .build();
        Response response = client.newCall(request).execute();
        Toast.makeText(getReactApplicationContext(), response.body().string(), duration).show();
    } catch (IOException e) {
        Toast.makeText(getReactApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
    }
}

Nachdem ich eine Anfrage erfolgreich gesendet hatte, wandte ich mich dem Senden einer angehefteten Anfrage zu.

Ich habe diese Pakete in meiner Datei verwendet

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.CertificatePinner;
import Java.io.IOException;

import Java.util.Map;
import Java.util.HashMap;

Kudos Herangehensweise war nicht klar, woher ich die öffentlichen Schlüssel bekomme oder wie ich sie generieren würde. Zum Glück gab okhttp3 docs neben einer klaren Demonstration der Verwendung des CertificatePinner an, dass ich zum Abrufen der öffentlichen Schlüssel nur eine Anforderung mit einer falschen PIN senden muss und die richtigen Pins angezeigt werden in der Fehlermeldung.

Nachdem ich mir einen Moment Zeit genommen hatte, um zu erkennen, dass OkHttpClent.Builder () verkettet werden kann und ich den CertificatePinner vor dem Build einbinden kann, im Gegensatz zu dem irreführenden Beispiel in Kudos Vorschlag (wahrscheinlich und einer älteren Version) kam ich mit dieser Methode.

@ReactMethod
public void getKeyChainForHost(String hostname, Callback errorCallbackContainingCorrectKeys,
  Callback successCallback) {
    try {
        CertificatePinner certificatePinner = new CertificatePinner.Builder()
             .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAA=")
             .build();
        OkHttpClient client = (new OkHttpClient.Builder()).certificatePinner(certificatePinner).build();

        Request request = new Request.Builder()
             .url("https://" + hostname)
             .build();
        Response response =client.newCall(request).execute();
        successCallback.invoke(response.body().string());
    } catch (Exception e) {
        errorCallbackContainingCorrectKeys.invoke(e.getMessage());
    }
}

Als ich dann die öffentlichen Schlüsselbunde ersetzte, die ich in den Fehler bekommen hatte, wurde der Text der Seite zurückgegeben, was darauf hinweist, dass ich eine erfolgreiche Anfrage gemacht hatte. Ich ändere einen Buchstaben des Schlüssels, um sicherzustellen, dass er funktioniert und ich wusste, dass ich auf dem richtigen Weg bin.

Ich hatte diese Methode schließlich in meiner ToastModule.Java-Datei

@ReactMethod
public void getKeyChainForHost(String hostname, Callback errorCallbackContainingCorrectKeys,
  Callback successCallback) {
    try {
        CertificatePinner certificatePinner = new CertificatePinner.Builder()
             .add(hostname, "sha256/+Jg+cke8HLJNzDJB4qc1Aus14rNb6o+N3IrsZgZKXNQ=")
             .add(hostname, "sha256/aR6DUqN8qK4HQGhBpcDLVnkRAvOHH1behpQUU1Xl7fE=")
             .add(hostname, "sha256/HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY=")
             .build();
        OkHttpClient client = (new OkHttpClient.Builder()).certificatePinner(certificatePinner).build();

        Request request = new Request.Builder()
             .url("https://" + hostname)
             .build();
        Response response =client.newCall(request).execute();
        successCallback.invoke(response.body().string());
    } catch (Exception e) {
        errorCallbackContainingCorrectKeys.invoke(e.getMessage());
    }
}

Android-Lösung Erweitert den OkHttpClient von React Native

Nachdem ich herausgefunden hatte, wie gesendete http-Anfrage gesendet werden konnte, war es gut. Jetzt kann ich die von mir erstellte Methode verwenden. Idealerweise dachte ich, es wäre am besten, den vorhandenen Client zu erweitern, um sofort den Nutzen der Implementierung zu erzielen.

Diese Lösung ist ab RN0.35 gültig, und ich weiß nicht, wie sie in Zukunft fair sein wird.

Bei der Suche nach Möglichkeiten zur Erweiterung des OkHttpClient für RN stieß ich auf diesen Artikel , in dem erklärt wurde, wie die TLS 1.2-Unterstützung durch Ersetzen der SSLSocketFactory hinzugefügt werden kann.

wenn ich es lernte, habe ich react gelernt, dass ein OkHttpClientProvider zum Erstellen der OkHttpClient-Instanz verwendet wird, die vom XMLHttpRequest-Objekt verwendet wird. Wenn wir diese Instanz ersetzen, würden wir daher das Pinning auf die gesamte App anwenden.

Ich habe eine Datei mit dem Namen OkHttpCertPin.Java zu meinem Android/app/src/main/Java/com/dreidev-Ordner hinzugefügt

package com.dreidev;

import Android.util.Log;

import com.facebook.react.modules.network.OkHttpClientProvider;
import com.facebook.react.modules.network.ReactCookieJarContainer;


import Java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.CertificatePinner;

public class OkHttpCertPin {
    private static String hostname = "*.efghermes.com";
    private static final String TAG = "OkHttpCertPin";

    public static OkHttpClient extend(OkHttpClient currentClient){
      try {
        CertificatePinner certificatePinner = new CertificatePinner.Builder()
             .add(hostname, "sha256/+Jg+cke8HLJNzDJB4qc1Aus14rNb6o+N3IrsZgZKXNQ=")
             .add(hostname, "sha256/aR6DUqN8qK4HQGhBpcDLVnkRAvOHH1behpQUU1Xl7fE=")
             .add(hostname, "sha256/HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY=")
             .build();
        Log.d(TAG, "extending client");
        return currentClient.newBuilder().certificatePinner(certificatePinner).build();
      } catch (Exception e) {
        Log.e(TAG, e.getMessage());
      }
     return currentClient;
   }
}

Dieses Paket verfügt über eine Methodenerweiterung, die einen vorhandenen OkHttpClient übernimmt und neu erstellt, indem er certificatePinner hinzufügt und die neu erstellte Instanz zurückgibt.

Anschließend habe ich meine MainActivity.Java-Datei nach der Hinweis dieser Antwort durch Hinzufügen der folgenden Methoden geändert

.
.
.
import com.facebook.react.ReactActivity;
import Android.os.Bundle;

import com.dreidev.OkHttpCertPin;
import com.facebook.react.modules.network.OkHttpClientProvider;
import okhttp3.OkHttpClient;

public class MainActivity extends ReactActivity {

  @Override
  public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     rebuildOkHtttp();
  }

  private void rebuildOkHtttp() {
      OkHttpClient currentClient = OkHttpClientProvider.getOkHttpClient();
      OkHttpClient replacementClient = OkHttpCertPin.extend(currentClient);
      OkHttpClientProvider.replaceOkHttpClient(replacementClient);
  }
.
.
.

Diese Lösung wurde zugunsten einer vollständigen Neuimplementierung der Methode "OkHttpClientProvider createClient" durchgeführt. Bei der Überprüfung des Anbieters erkannte ich, dass die Master-Version die TLS 1.2-Unterstützung implementiert hatte, jedoch noch nicht zur Verfügung stand Der Wiederaufbau erwies sich als das beste Mittel zur Erweiterung des Kunden. Ich frage mich, wie dieser Ansatz beim Upgrade richtig sein wird, aber im Moment funktioniert es gut.

Update Ab 0.43 scheint dieser Trick nicht mehr zu funktionieren. Aus zeitlichen Gründen werde ich mein Projekt vorerst bei 0,42 einfrieren, bis der Grund für den Stillstand des Wiederaufbaus klar ist.

Lösung IOS

For IOS Ich hatte gedacht, ich müsste eine ähnliche Methode anwenden, wobei ich wieder mit Kudos Vorschlag als Leitfaden anfange.

Beim Überprüfen des RCTNetwork-Moduls erfuhr ich, dass NSURLConnection verwendet wurde. Statt zu versuchen, ein vollständig neues Modul mit AFNetworking zu erstellen, wie in dem von mir gefundenen Vorschlag TrustKit vorgeschlagen.

dem Getting Started Guide folgend habe ich einfach hinzugefügt

pod 'TrustKit'

zu meiner podfile und lief pod install

im GettingStartedGuide wurde erklärt, wie ich diesen Pod von meiner pList.file aus konfigurieren kann, aber ich möchte lieber Code als Konfigurationsdateien verwenden. Ich fügte der Datei AppDelegate.m die folgenden Zeilen hinzu

.
.
.
#import <TrustKit/TrustKit.h>
.
.
.
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{


  // Initialize TrustKit
  NSDictionary *trustKitConfig =
    @{
    // Auto-swizzle NSURLSession delegates to add pinning validation
    kTSKSwizzleNetworkDelegates: @YES,

    kTSKPinnedDomains: @{

       // Pin invalid SPKI hashes to *.yahoo.com to demonstrate pinning failures
       @"efghermes.com" : @{
           kTSKEnforcePinning:@YES,
           kTSKIncludeSubdomains:@YES,
           kTSKPublicKeyAlgorithms : @[kTSKAlgorithmRsa2048],

           // Wrong SPKI hashes to demonstrate pinning failure
           kTSKPublicKeyHashes : @[
              @"+Jg+cke8HLJNzDJB4qc1Aus14rNb6o+N3IrsZgZKXNQ=",
              @"aR6DUqN8qK4HQGhBpcDLVnkRAvOHH1behpQUU1Xl7fE=",
              @"HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY="
              ],

          // Send reports for pinning failures
          // Email [email protected] if you need a free dashboard to see your App's reports
          kTSKReportUris: @[@"https://overmind.datatheorem.com/trustkit/report"]
          },

     }
  };

  [TrustKit initializeWithConfiguration:trustKitConfig];
.
.
.

Ich habe die Public-Key-Hashes von meiner Android-Implementierung erhalten und es hat einfach funktioniert (die Version von TrustKit, die ich in meinen Pods erhalten habe, ist 1.3.2).

Ich war froh IOS stellte sich als Atemzug heraus

Als Randbemerkung hat TrustKit darauf hingewiesen, dass Auto-swizzle nicht funktioniert, wenn NSURLSession und Connection bereits umgestellt sind. das sagte, es scheint bis jetzt gut zu funktionieren.

Fazit

Diese Antwort stellt die Lösung sowohl für Android als auch für IOS dar, da ich diese in nativem Code implementieren konnte.

Eine mögliche Verbesserung kann darin bestehen, ein gemeinsames Plattformmodul zu implementieren, bei dem das Festlegen öffentlicher Schlüssel und das Konfigurieren der Netzanbieter von Android und IOS in Javascript verwaltet werden können.

Vorschlag von Kudo Wenn Sie einfach die öffentlichen Schlüssel zum js-Bundle hinzufügen, kann dies jedoch eine Sicherheitsanfälligkeit darstellen, bei der die Bundle-Datei irgendwie ersetzt werden kann.Ich weiß nicht, wie dieser Angriffsvektor funktionieren kann, aber der zusätzliche Schritt des Signierens des bundle.js wie vorgeschlagen kann das js-Bundle schützen.

Ein anderer Ansatz könnte darin bestehen, das js-Bundle einfach in eine 64-Bit-Zeichenfolge zu codieren und direkt in den systemeigenen Code als in der Unterhaltung dieser Ausgabe erwähnt einzugeben. Dieser Ansatz hat den Vorteil, dass das js-Bundle in der App fest verdrahtet wird, sodass es für Angreifer unzugänglich ist.

Wenn Sie bis hierher gelesen haben, hoffe ich, dass ich Sie bei Ihrer Suche zur Behebung Ihres Fehlers erleuchtet habe und wünschte, Sie würden einen sonnigen Tag genießen.

If you read this far I hope I enlightened you on your quest for fixing your bug and wish you enjoy a sunny day.

39
Amr Draz

Sie können diese lib https://github.com/nlt2390/react-native-pinning-ssl verwenden.

Es überprüft die SSL-Verbindung mit SHA1-Schlüsseln, nicht mit Zertifikaten.

0
leLabrador