Comment puis-je implémenter L'épinglage de certificat SSL tout en utilisant React Native

je dois implémenter L'épinglage de certificat SSL dans mon application React native.

je sais très peu de choses sur SSL / TLS et encore moins sur le pinning. Je ne suis pas non plus un développeur mobile natif, bien que je connaisse Java et ai appris Objectif-C sur ce projet assez pour se déplacer.

j'ai commencé à chercher comment exécuter cette tâche.

ne réagit pas natif déjà mettre en œuvre cela?

Non, Ma recherche initiale me conduire à l' cette proposition qui a n'a reçu aucune activité depuis le 2 août 2016.

de là j'ai appris que react-native utilise OkHttp qui ne supporte pas L'épinglage, mais je ne serais pas en mesure de le retirer de Javascript, qui n'est pas vraiment une exigence mais un plus.

L'implémenter en Javascript.

alors que react semble utiliser l'exécution nodejs, c'est plus un navigateur qu'un noeud, ce qui signifie qu'il ne supporte pas tous les modules natifs, en particulier le module https, pour lequel j'avais implémenté certificat indiquant la suite cet article. Ne pouvait donc pas le transporter dans react native.

j'ai essayé d'utiliser rn-nodeify mais les modules n'ont pas fonctionné. Cela a été vrai depuis RN 0.33 à RN 0.35 que je suis actuellement sur.

implémenter en utilisant le plugin phonegap

j'ai pensé à utiliser un phongape-plugin cependant puisque j'ai une dépendance sur les bibliothèques qui nécessitent react 0.32 + Je ne peux pas utiliser réagissent-native-cordoue-plugin

Juste le faire en natif

bien que je ne sois pas un développeur d'applications natif, je peux toujours essayer, mais ce n'est qu'une question de temps.

Android a un certificat d'épinglage

j'ai appris que android supporte SSL Pinning cependant a échoué car il semble que cette approche ne fonctionne pas avant Android 7. En plus de ne travailler que pour android.

bas ligne

j'ai épuisé plusieurs directions et je vais continuer à poursuivre plus d'implémentation native, peut-être trouver comment configurer OkHttp et RNNetworking puis peut-être revenir à react-native.

mais y a-t-il déjà des implémentations ou des guides pour iOS et android?

25
demandé sur Amr Draz 2016-10-25 15:44:03

2 réponses

après avoir épuisé le spectre actuel des options disponibles à partir de Javascript, j'ai décidé de simplement implémenter le pointage de certificat nativement.tout semble si simple maintenant que j'ai terminé.

Passer à l'en-tête intitulé Android Solution et Solution IOS si vous ne voulez pas lire à travers le processus d'atteindre la solution.

Android

Suivant recommandation de Kudo je pensais sortie pour implémenter le pinning en utilisant okhttp3.

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

j'ai d'abord commencé par apprendre à créer un natif android pont à réagir natif création d'un module toast. J'ai ensuite étendu avec une méthode pour envoyer une simple demande

@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();
    }
}

réussissant à envoyer une requête, je me suis alors tourné vers l'envoi d'une requête épinglée.

j'ai utilisé ces paquets dans mon fichier

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;

L'approche de Kudo n'était pas claire sur l'endroit où j'obtiendrais le clés publiques ou comment les créer. heureusement okhttp3 docs en plus de fournir une démonstration claire de la façon d'utiliser le CertificatePinner a déclaré que pour obtenir les clés publiques, tout ce que je dois faire est d'envoyer une demande avec un NIP incorrect, et les pins corrects apparaîtront dans le message d'erreur.

après avoir pris un moment pour réaliser que OkHttpClent.Builder() peut être enchaîné et je peux inclure le CertificatePinner avant la construction, contrairement à l'exemple trompeur dans Proposition de Kudo (probablement et version plus ancienne) je suis venu avec cette méthode.

@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());
    }
}

puis, en remplaçant les clés publiques que j'ai eu dans l'erreur, j'ai rendu le corps de la page, indiquant que j'avais fait une demande réussie, je change une lettre de la clé pour m'assurer qu'elle fonctionnait et je savais que j'étais sur la bonne voie.

j'ai finalement eu cette méthode dans mon ToastModule.fichier java

@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());
    }
}

solution Android Extending React Native's OkHttpClient

avoir compris comment envoyer une requête http épinglée était une bonne chose, maintenant je peux utiliser la méthode que j'ai créée, mais idéalement j'ai pensé qu'il serait préférable d'étendre le client existant, afin de bénéficier immédiatement de l'implémentation.

Cette solution est valide RN0.35 et je ne sais pas comment il va juste dans l'avenir.

en recherchant des moyens d'étendre L'OkHttpClient pour RN je suis tombé sur cet article expliquant comment ajouter le support TLS 1.2 en remplaçant SSLSocketFactory.

reading it I learned react utilise un OkHttpClientProvider pour créer L'instance OkHttpClient utilisée par L'objet XMLHttpRequest et donc si nous remplaçons cette instance, nous appliquerions le pinning à toute l'application.

j'ai ajouté un fichier appelé OkHttpCertPin.java mon android/app/src/main/java/com/dreidev dossier

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;
   }
}

ce paquet a une méthode extend qui prend un OkHttpClient existant et le reconstruit en ajoutant le certificatePinner et renvoie l'instance nouvellement construite.

j'ai alors modifié mon activité principale.le fichier java suivant cette réponse du conseil en ajoutant les méthodes suivantes

.
.
.
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);
  }
.
.
.

Cette solution a été réalisée en faveur de la réimplantation complète de la méthode okhttpclientprovider createClient, car en inspectant le fournisseur j'ai réalisé que la version master avait mis en oeuvre le soutien des SDF 1.2 mais n'était pas encore une option disponible pour moi la reconstruction a donc été considérée comme le meilleur moyen d'étendre le client. Je me demande comment cette approche sera équitable que je mets à niveau, mais pour l'instant, il fonctionne bien.

mise à Jour il semble que commencer 0.43 cette astuce ne fonctionne plus. Pour des raisons temporelles, je vais geler mon projet à 0.42 pour l'instant, jusqu'à ce que la raison pour laquelle la reconstruction a cessé de fonctionner soit claire.

Solution IOS

Pour IOS j'avais pensé que j'aurais besoin de suivre un similaire méthode, encore une fois en commençant par la proposition de Kudo comme mon chef.

inspection du module RCTNetwork j'ai appris que Nurlconnection a été utilisé, donc au lieu d'essayer de créer un tout nouveau module avec AFNetworking comme suggéré dans la proposition j'ai découvert TrustKit

suivant son Guide de démarrage j'ai simplement ajouté

pod 'TrustKit'

à mon podfile et a couru pod install

le guide Gettingstartedeguide explique comment je peux configurer cette capsule de mon pList.fichier mais préférant utiliser du code que des fichiers de configuration, j'ai ajouté les lignes suivantes à mon AppDelegate.m le fichier

.
.
.
#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 info@datatheorem.com if you need a free dashboard to see your App's reports
          kTSKReportUris: @[@"https://overmind.datatheorem.com/trustkit/report"]
          },

     }
  };

  [TrustKit initializeWithConfiguration:trustKitConfig];
.
.
.

j'ai reçu les clés publiques de mon implémentation android et ça a fonctionné (la version de TrustKit que j'ai reçue dans Mes pods est 1.3.2)

j'ai été heureux d'IOS s'est avéré être un souffle

comme une note de côté TrustKit averti que c'est auto-swizzle ne fonctionnera pas si la Nslsession et la connexion sont déjà swizzled. cela dit, il semble bien fonctionner jusqu'à présent.

Conclusion

cette réponse présente la solution à la fois pour Android et IOS, étant donné que j'ai été en mesure de l'implémenter en code natif.

une amélioration possible pourrait être d'implémenter un module commun de plate-forme où la configuration des clés publiques et la configuration des fournisseurs de réseau d'android et D'IOS peuvent être gérées en javascript.

proposition de Kudo mentionné le simple fait d'ajouter les clés publiques au paquet js peut cependant exposer une vulnérabilité, où d'une manière ou d'une autre le fichier du paquet peut être remplacé.

Je ne sais pas comment ce vecteur d'attaque peut fonctionner, mais certainement l'étape supplémentaire de signer le paquet.js proposé peut protéger la js bundle.

une autre approche pourrait être de simplement encoder le paquet js dans une chaîne 64 bits et l'inclure dans le code natif directement comme mentionné dans ce numéro conversation. Cette approche a l'avantage de brouiller les pistes et de connecter le paquet js à l'application, ce qui le rend inaccessible pour les attaquants, je pense.

si vous avez lu jusqu'ici, j'espère que je vous ai éclairé sur votre quête pour corriger votre bug et je vous souhaite une belle journée ensoleillée.

35
répondu Amr Draz 2017-07-14 09:45:33

Vous pouvez utiliser cette lib https://github.com/nlt2390/react-native-pinning-ssl

il vérifie la connexion SSL en utilisant les clés SHA1, pas les certificats.

0
répondu leLabrador 2018-08-17 09:11:21