反応ネイティブアプリケーションにSSL証明書ピン留めを実装する必要があります。
ピン留めはもちろん、SSL / TLSについてもほとんど知りません。私はネイティブのモバイル開発者でもありませんが、Javaを知っており、このプロジェクトでObjective-Cを十分に習得しました。
このタスクの実行方法を探し始めました。
いいえ、最初の検索で、2016年8月2日以降何も活動していないこの提案にたどり着きました。
それから、react-nativeがPinningをサポートするOkHttpを使用していることを学びましたが、JavaScriptからそれを引き出すことができません。これは、実際には要件ではなくプラスです。
reactはnodejsランタイムを使用しているように見えますが、nodeではなくブラウザのようなものです。つまり、すべてのネイティブモジュール、特にこの記事に従って証明書の固定を実装したhttpsモジュールをサポートしているわけではありません。したがって、ネイティブに反応させることはできませんでした。
rn-nodeifyを使用してみましたが、モジュールが機能しませんでした。これは、私が現在取り組んでいるRN 0.33からRN 0.35まで当てはまります。
私はphongape-pluginを使用することを考えましたが、反応0.32+を必要とするライブラリに依存しているため、react-native-cordova-pluginを使用できません
私はネイティブアプリの開発者ではありませんが、いつでも問題を解くことができます。
AndroidがSSLピン接続をサポートしていることを知りましたが、Android 7より前ではこのアプローチは機能せず、Androidでのみ機能するようで、失敗しました。
私はいくつかの方向を尽くしてきましたが、よりネイティブな実装を追求し続けます。おそらくOkHttpとRNNetworkingを構成する方法を理解し、それからリアクションネイティブにブリッジするかもしれません。
しかし、IOSとandroidの実装またはガイドはすでにありますか?
Javascriptから利用可能なオプションの現在のスペクトルを使い果たした後、私は証明書の固定をネイティブに実装することを決定しました。
ソリューションに到達するプロセスを読みたくない場合は、Android SolutionおよびIOS Solutionというタイトルのヘッダーにスキップしてください。
工藤の勧めに従って、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();
最初に、反応するネイティブでトーストモジュールを作成するネイティブ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のドキュメントに加えて、CertificatePinnerの使用方法の明確なデモンストレーションを提供し、公開キーを取得するには、間違ったピンを使用してリクエストを送信するだけでエラーメッセージに正しいピンが表示されると述べています。
Kudoの提案(おそらく古いバージョン)の誤解を招く例とは異なり、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());
}
}
固定されたhttpリクエストを送信する方法がわかったので、作成した方法を使用できるようになりましたが、理想的には、既存のクライアントを拡張して実装の利点をすぐに得ることが最善だと思いました。
このソリューションは現在有効でRN0.35
あり、将来どのように公正になるかわかりません。
RN用のOkHttpClientを拡張する方法を検討しているときに、SSLSocketFactoryを置き換えることによってTLS 1.2サポートを追加する方法を説明するこの記事に出くわしました。
それを読んで、reactはOkHttpClientProviderを使用してXMLHttpRequestオブジェクトが使用するOkHttpClientインスタンスを作成しているため、そのインスタンスを置き換えると、すべてのアプリに固定が適用されます。
私はと呼ばれるファイルを追加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を追加して再構築するメソッド拡張があり、新しく構築されたインスタンスを返します。
次に、この回答のアドバイスに従って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サポートが実装されているが、まだ使用できるオプションがないことがわかったため、再構築がクライアントを拡張する最良の方法。このアプローチがアップグレード時にどのように公平になるか疑問に思っていますが、今のところそれはうまく機能しています。
アップデート 0.43以降、このトリックは機能しなくなったようです。時間的な制約があるため、リビルドが機能しなくなった理由が明らかになるまで、今のところプロジェクトを0.42に凍結します。
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が呼吸であることが判明してうれしかった
補足として、NSURLSessionとConnectionが既にスウィズルされている場合、自動スウィズルは機能しないことをTrustKitは警告しました。それは今のところうまく機能しているようだと言った。
この回答は、AndroidとIOSの両方のソリューションを示しています。これをネイティブコードで実装できたとすれば、
考えられる改善の1つは、公開キーの設定とAndroidとIOSの両方のネットワークプロバイダーの設定をJavaScriptで管理できる共通プラットフォームモジュールを実装することです。
ただし、公開鍵をjsバンドルに追加するだけであると述べた工藤の提案には、バンドルファイルが何らかの方法で置き換えられる可能性があるという脆弱性が存在する可能性があります。
その攻撃ベクトルがどのように機能するかはわかりませんが、提案されているbundle.jsに署名するという追加の手順によって、jsバンドルが保護される可能性があります。
別のアプローチとしては、jsバンドルを64ビット文字列に単純にエンコードし、この問題の会話で述べたように、ネイティブコードに直接含める方法があります。このアプローチには、jsバンドルを難読化し、アプリにハードワイヤリングするという利点があり、攻撃者がアクセスできないようになっていると思います。
ここまで読んだら、バグを修正するための探求について啓蒙し、晴れた日を楽しんでほしいと思います。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加