web-dev-qa-db-ja.com

React Native

反応するネイティブアプリケーションにSSL証明書のピン留めを実装する必要があります。

ピン留めはもちろんのこと、SSL/TLSについてはほとんど知りません。私はネイティブのモバイル開発者でもありませんが、Javaを知っていて、このプロジェクトでObjective-Cを十分に学習しました。

このタスクを実行する方法を探し始めました。

React Nativeはこれを既に実装していますか?

いいえ、最初の検索で この提案 に移動しました。これは2016年8月2日以降何の活動も受けていません。

それから、react-nativeはPinningをサポートするOkHttpを使用することを学びましたが、Javascriptからそれを引き出すことはできません。これは実際には要件ではなくプラスです。

Javascriptで実装します。

反応はnodejsランタイムを使用しているように見えますが、ノードよりもブラウザのようなものです。つまり、すべてのネイティブモジュール、具体的にはhttpsモジュールをサポートしていません。 この記事 。したがって、それをネイティブに反応させることができませんでした。

Rn-nodeifyを使用しようとしましたが、モジュールは機能しませんでした。これは、私が現在取り組んでいるRN 0.33からRN 0.35まで真実です。

Phonegapプラグインを使用して実装する

phongape-plugin を使用することを考えましたが、react 0.32+を必要とするライブラリに依存しているため、 react-native-cordova-plugin を使用できません

ネイティブでやる

私はネイティブアプリの開発者ではありませんが、時間の問題でいつでもそれに挑戦することができます。

Androidには証明書のピン留めがあります

Androidは SSL Pinning をサポートしますが、このアプローチは機能しないと思われるため、失敗しましたAndroid 7。 Androidでのみ機能します。

結論

私はいくつかの方向を使い尽くし、よりネイティブな実装を追求し続けます。OkHttpとRNNetworkingを構成する方法を見つけてから、react-nativeにブリッジバックするかもしれません。

しかし、IOS and android?

35
Amr Draz

Javascriptから利用可能なオプションの現在のスペクトルを使い果たした後、証明書のピン留めをネイティブに実装することにしました。

Android SolutionおよびIOS Solutionというタイトルのヘッダーにスキップしないソリューションに到達するプロセスを読みたい。

Android

次の 工藤の推奨 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();

最初に、ネイティブを作成する方法を学ぶことから始めました react nativeを使用したAndroidブリッジ トーストモジュールの作成。次に、単純なリクエストを送信するメソッドで拡張しました

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

リクエストの送信に成功してから、固定されたリクエストの送信に切り替えました。

これらのパッケージをファイルで使用しました

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;

工藤のアプローチは、公開鍵をどこで入手するか、またはそれらを生成する方法について明確ではありませんでした。幸運にも okhttp3 docs CertificatePinnerを使用する方法の明確なデモンストレーションを提供することに加えて、公開キーを取得するために必要なことは、間違ったピンでリクエストを送信し、正しいピンがエラーメッセージに表示されます。

OkHttpClent.Builder()をチェーン化して、ビルドの前にCertificatePinnerを含めることができることに気づいた後、工藤の提案(おそらく以前のバージョン)の誤解を招く例とは異なり、このメソッドを思いつきました。

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

その後、エラーで取得した公開キーチェーンを交換すると、ページの本文が返され、リクエストが成功したことを示します。キーの1文字を変更して、機能していることを確認し、順調に進んでいることを確認しました。

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

Androidソリューションの拡張React NativeのOkHttpClient

固定されたhttpリクエストを送信する方法がわかったので、作成したメソッドを使用できるようになりましたが、理想的には、既存のクライアントを拡張して、すぐに実装の利点を得ることが最善だと考えました。

このソリューションはRN0.35の時点で有効であり、今後どのように公平になるかわかりません。

RN用のOkHttpClientを拡張する方法を検討していると、 この記事 SSLSocketFactoryを置き換えることでTLS 1.2サポートを追加する方法を説明しました。

それを読んで、私が学んだ反応は、XMLHttpRequestオブジェクトによって使用されるOkHttpClientインスタンスを作成するためにOkHttpClientProviderを使用するため、そのインスタンスを置き換える場合、すべてのアプリにピン留めを適用します。

OkHttpCertPin.Javaというファイルを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;
   }
}

このパッケージには、既存のOkHttpClientを取得し、certificatePinnerを追加して再構築し、新しく構築されたインスタンスを返すメソッドextendがあります。

次に、次のメソッドを追加して、 この回答のアドバイス に従ってMainActivity.Javaファイルを変更しました

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

このソリューションは、OkHttpClientProvider createClientメソッドを完全に再実装するために実行されました。プロバイダーを調べると、 マスターバージョン はTLS 1.2サポートを実装していましたが、まだ使用できるオプションではなかったことがわかりました。そのため、再構築がクライアントを拡張する最良の手段であることがわかりました。私はアップグレードするとこのアプローチがどのように公平になるのだろうかと思っていますが、今のところはうまくいきます。

Update0.43以降、このトリックは機能しなくなったようです。期限付きの理由から、再構築が機能しなくなった理由が明らかになるまで、当面はプロジェクトを0.42でフリーズします。

ソリューションIOS

IOSの場合は、同様の方法に従う必要があると考えていましたが、再びリードとして工藤の提案から始めます。

RCTNetworkモジュールを調べると、NSURLConnectionが使用されていることがわかりました。そのため、私が発見した提案で提案したように、AFNetworkingで完全に新しいモジュールを作成しようとする代わりに TrustKit

入門ガイドに従って、私は単に追加しました

pod 'TrustKit'

私のポッドファイルにpod installを実行しました

gettingStartedGuideは、pList.fileからこのポッドを構成する方法を説明しましたが、構成ファイルよりもコードを使用することを好み、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];
.
.
.

Androidの実装から公開キーハッシュを取得し、動作しました(ポッドで受け取ったTrustKitのバージョンは1.3.2です)

嬉しかったIOS息ができた

サイドノートとして、TrustKitは、NSURLSessionとConnectionがすでにスウィズルされている場合、自動スウィズルは機能しないと警告しました。それは今のところうまく機能しているようだと言った。

結論

この答えは、ネイティブコードでこれを実装できた場合、AndroidとIOSの両方のソリューションを示します。

考えられる改善の1つは、共通キーを設定し、AndroidとIOSの両方でネットワークプロバイダーを設定できる共通のプラットフォームモジュールを実装することです。

工藤の提案 jsバンドルに公開鍵を追加するだけで、バンドルファイルを何らかの方法で置き換えることができる脆弱性が公開される可能性があります。

その攻撃ベクトルがどのように機能するかはわかりませんが、確かに、提案されたbundle.jsに署名する追加のステップがjsバンドルを保護する可能性があります。

別のアプローチは、jsバンドルを単純に64ビット文字列にエンコードし、ネイティブコードに直接 この号の会話で言及 として含めることです。このアプローチには、jsバンドルを難読化するだけでなく、アプリにハードワイヤリングするという利点があり、攻撃者がアクセスできないようにしています。

ここまで読んでいただければ、バグを修正するための探求について啓発し、晴れた日をお楽しみください。

45
Amr Draz

このライブラリを使用できます https://github.com/nlt2390/react-native-pinning-ssl

証明書ではなく、SHA1キーを使用してSSL接続を検証します。

0
leLabrador