web-dev-qa-db-fra.com

Comment implémenter l'identification du certificat SSL en utilisant React Native

Je dois implémenter SSL Certificate Pinning dans mon application native.

Je connais 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, même si je connais assez Java et que j'ai assez appris sur Objective-C pour ce projet.

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

React Native n'implémente-t-il pas déjà cela?

Non, ma recherche initiale m'a conduit à cette proposition qui n'a plus eu d'activité depuis le 2 août 2016.

J'y ai appris que react-native utilise OkHttp, qui prend en charge Pinning, mais je ne serais pas en mesure de le retirer de Javascript, ce qui n'est pas vraiment une exigence, mais un avantage.

Implémentez-le en Javascript.

Bien que react semble utiliser le runtime nodejs, il ressemble plus à un navigateur qu’à un noeud, ce qui signifie qu’il ne prend pas en charge tous les modules natifs, en particulier le module https, pour lequel j’ai implémenté le certificat épinglant après cet article . Ainsi, ne pouvait pas le porter en réaction natif.

J'ai essayé d'utiliser rn-nodeify mais les modules ne fonctionnaient pas. Cela est vrai depuis RN 0.33 à RN 0.35 sur lequel je suis actuellement.

Implémenter en utilisant le plugin phonegap

J'ai pensé utiliser un phongape-plugin cependant, étant donné que je suis dépendant de bibliothèques nécessitant une réaction de 0.32 ou plus, je ne peux pas utiliser react-native-cordova-plugin

Faites-le nativement

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 épingler un certificat

J'ai appris qu'Android prend en charge SSL Pinning a cependant échoué, car il semble que cette approche ne fonctionne pas avant Android 7. Elle ne fonctionne que pour Android.

La ligne du bas

J'ai épuisé plusieurs pistes et je vais continuer à poursuivre une implémentation plus native, peut-être comprendre comment configurer OkHttp et RNNetworking, puis peut-être revenir à une configuration native.

Mais existe-t-il déjà des implémentations ou des guides pour IOS et Android?

29
Amr Draz

Après avoir épuisé le spectre actuel des options disponibles en Javascript, j'ai décidé de simplement mettre en œuvre un certificat, car cela semble si simple maintenant que j'ai terminé.

Passez aux en-têtes intitulés Solution Android et IOS Solution si vous ne voulez pas lire le processus d’atteinte de la solution.

Android

Suivant la recommandation de Kudo , j'ai pensé mettre en œuvre l'épinglage à l'aide de 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 pont natif Android avec Réagir natif pour créer un module de pain grillé. Je l'ai ensuite étendu avec une méthode d'envoi d'une demande simple

@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 ensuite tourné vers l’envoi d’une requête épinglée.

J'ai utilisé ces paquets dans mon dossier

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 quant à l'endroit où j'obtiendrais les clés publiques ou comment les générer. heureusement, okhttp3 docs , en plus de fournir une démonstration claire de l’utilisation du CertificatePinner, a déclaré que pour obtenir les clés publiques, il me suffisait d’envoyer une demande avec une broche incorrecte, et les bonnes Message d'erreur.

Après avoir pris un moment pour me rendre compte que OkHttpClent.Builder () peut être chaîné et que je peux inclure le CertificatePinner avant la construction, contrairement à l'exemple trompeur de la proposition de Kudo (version probablement ou antérieure), je suis arrivé à 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 trousseaux publics que j'ai générés dans l'erreur, le corps de la page a été restitué, indiquant que ma demande avait été acceptée, je modifiais une lettre de la clé pour m'assurer qu'elle fonctionnait correctement et que je savais que j'étais sur la bonne voie.

J'ai finalement eu cette méthode dans mon fichier ToastModule.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 étendant OkHttpClient de React Native

Après avoir compris comment envoyer une demande http épinglée était une bonne chose, je peux maintenant utiliser la méthode que j'ai créée, mais idéalement, je pensais qu'il serait préférable d'étendre le client existant pour bénéficier immédiatement de la mise en œuvre.

Cette solution est valable à partir du RN0.35 et je ne sais pas comment elle sera équitable dans le futur.

En étudiant les moyens d'étendre OkHttpClient for RN, je suis tombé sur cet article en expliquant comment ajouter la prise en charge de TLS 1.2 en remplaçant SSLSocketFactory.

en le lisant, j'ai appris que react utilise un OkHttpClientProvider pour créer l'instance OkHttpClient utilisée par l'objet XMLHttpRequest et que, par conséquent, si nous remplaçons cette instance, nous appliquerons le pinning à l'ensemble de l'application.

J'ai ajouté un fichier appelé OkHttpCertPin.Java à mon dossier Android/app/src/main/Java/com/dreidev

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 extension de méthode qui prend un OkHttpClient existant et le reconstruit en ajoutant le certificatePinner et retourne l'instance nouvellement construite.

J'ai ensuite modifié mon fichier MainActivity.Java en suivant le conseil de cette réponse 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 d'une réimplémentation complète de la méthode createClient d'OkHttpClientProvider, car en inspectant le fournisseur, j'ai réalisé que la version principale avait implémenté la prise en charge de TLS 1.2 mais n'était pas encore une option disponible pour moi. jugé le meilleur moyen d’allonger le client. Je me demande comment cette approche fonctionnera correctement à mesure que j'effectue une mise à niveau, mais pour l'instant cela fonctionne bien.

Mise à jour Il semble que cette astuce ne fonctionne plus à partir de 0,43. Pour des raisons temporelles, je vais geler mon projet à 0,42 pour le moment, jusqu'à ce que la raison pour laquelle la reconstruction ait cessé de fonctionner soit clairement définie.

Solution IOS

Pour IOS, j'avais pensé que je devrais suivre une méthode similaire, en commençant à nouveau par la proposition de Kudo.

Vérification du module RCTNetwork J'ai appris que NSURLConnection était utilisée. 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 mise en route, j’ai simplement ajouté

pod 'TrustKit'

à mon podfile et a couru pod install

gettingStartedGuide a expliqué comment configurer ce pod à partir de mon fichier pList.file, mais préférant utiliser du code plutôt que des fichiers de configuration, j'ai ajouté les lignes suivantes à mon fichier AppDelegate.m

.
.
.
#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];
.
.
.

J'ai obtenu les clés de hachage de la clé publique de mon implémentation Android et cela fonctionnait (la version de TrustKit que j'ai reçue dans mes pods est la 1.3.2)

J'étais content que IOS se soit avéré être un souffle

En guise de remarque, TrustKit a prévenu que son mode de balayage automatique ne fonctionnerait pas si NSURLSession et Connection étaient déjà en train de fonctionner. Cela dit, cela semble bien fonctionner jusqu'à présent.

Conclusion

Cette réponse présente la solution pour Android et IOS, étant donné que j'ai pu l'implémenter en code natif.

Une amélioration possible pourrait consister à implémenter un module de plate-forme commune dans lequel la définition de clés publiques et la configuration des fournisseurs de réseau à la fois pour Android et IOS peuvent être gérées en javascript.

La proposition de Kudo mentionnée simplement ajouter des clés publiques au bundle js peut toutefois exposer une vulnérabilité, dans laquelle le fichier de bundle peut être remplacé.

Je ne sais pas comment ce vecteur d’attaque peut fonctionner, mais l’étape supplémentaire consistant à signer le bundle.js tel que proposé peut protéger le bundle js.Une autre approche peut consister simplement à coder le paquet js en une chaîne de 64 bits et à l’inclure directement dans le code natif en tant que mentionné dans la conversation de ce problème . Cette approche présente l’avantage d’obscurcir le câblage du bundle js dans l’application, le rendant ainsi inaccessible aux attaquants ou du moins, à mon avis.

Si vous lisez jusque-là, j'espère vous avoir éclairé dans votre quête de la correction de votre bug et vous souhaite de profiter d'une journée ensoleillée.

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

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

Il vérifie la connexion SSL à l'aide de clés SHA1 et non de certificats.

0
leLabrador