使用React Native时如何实现SSL证书固定 [英] How can I implement SSL Certificate Pinning while using React Native

查看:336
本文介绍了使用React Native时如何实现SSL证书固定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在我的本机应用程序中实现SSL证书固定.

I need to implement SSL Certificate Pinning in my react native application.

我对SSL/TLS知之甚少,更不用说固定了. 我也不是本地移动开发人员,尽管我了解Java,并且在该项目上学到了Objective-C足以解决问题.

I know very little about SSL/TLS let alone pinning. I am also not a native mobile developer, though I know Java and learned Objective-C on this project enough to get around.

我开始搜索如何执行此任务.

I started searching for how to execute this task.

不,我的初步搜索将我带到此提案,其中包含自2016年8月2日以来没有任何活动.

No, My initial search lead me to this proposal which has received no activity since August 2nd 2016.

从中我了解到react-native使用了确实支持Pinning的OkHttp,但是我无法将其从Javascript中提取出来,这并不是真正的要求,而是一个加号.

From it I learned that react-native uses OkHttp which does support Pinning, but I wouldn't be able to pull it off from Javascript, which is not really a requirement but a plus.

虽然反应似乎使用了nodejs运行时,但它更像是一个浏览器而不是节点,这意味着它不支持所有本机模块,特别是https模块,我已经在

While react seems like it uses the nodejs runtime, it is more like a browser than node, meaning it does not support all native modules, specifically the https module, for which I had implemented certificate pinning following this article. Thus could not carry it into react native.

我尝试使用rn-nodeify,但是模块不起作用.自从我目前在RN 0.33到RN 0.35以来,情况就是如此.

I tried using rn-nodeify but the modules didn't work. This has been true since RN 0.33 to RN 0.35 which I'm currently on.

我考虑使用 phongape-plugin ,但是因为我对库有依赖性需要反应0.32+,我不能使用 react-native-cordova-plugin

I thought of using a phongape-plugin however since I have a dependency on libraries that require react 0.32+ I can't use react-native-cordova-plugin

虽然我不是本机应用程序开发人员,但我总是可以解决它,只是时间问题.

While I'm not a native app developer I can always take a crack at it, only a matter of time.

我了解到android支持 SSL固定,但是并没有成功在Android 7之前的版本中,这种方法似乎不起作用,而且仅适用于android.

I learned that android supports SSL Pinning however was unsuccessful as it seems that this approach does not work Prior to Android 7. As well as only working for android.

我已经精疲力竭了几个方向,并将继续追求更多的本机实现,也许想出如何配置OkHttp和RNNetworking,然后再桥接到本机.

I have exhausted several directions and will continue to pursue more native implementation, maybe figure out how to configure OkHttp and RNNetworking then maybe bridging back to react-native.

但是对于IOS和android已经有任何实现或指南了吗?

But is there already any implementations or guide for IOS and android?

推荐答案

在用尽了Javascript的可用选项的最新范围之后,我决定简单地以本机方式实现证书固定,现在看来一切都变得如此简单.

After exhausting the current spectrum of available options from Javascript I decided to simply implement certificate pinning natively it all seems so simple now that I'm done.

如果您不想通读解决方案的过程,请跳至标题为 Android解决方案 IOS解决方案的标题.

Android

遵循 Kudo的建议我考虑过要使用okhttp3来实现固定.

Android

Following Kudo's recommendation I thought out to implement pinning using 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 bridge与当地人互动创建烤面包模块.然后,我使用一种发送简单请求的方法对其进行了扩展

I first started by learning how to create a native android bridge with react nativecreating a toast module. I then extended it with a method for sending a simple request

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

成功发送请求后,我转向发送固定的请求.

Succeeding in sending a request I then turned to sending a request pinned.

我在文件中使用了这些软件包

I used these packages in my file

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's approach wasn't clear on where I would get the public keys or how to generate them. luckily okhttp3 docs in addition to providing a clear demonstration of how to use the CertificatePinner stated that to get the public keys all I would need to do is send a request with an incorrect pin, and the correct pins will appear in the error message.

花了一点时间意识到可以链接OkHttpClent.Builder(),并且可以在构建之前包含CertificatePinner,这与Kudo提案(可能是较旧的版本)中的误导性示例不同,我想出了这种方法.

After taking a moment to realise that OkHttpClent.Builder() can be chained and I can include the CertificatePinner before the build, unlike the misleading example in Kudo's proposal (probably and older version) I came up with this method.

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

然后替换我在错误中遇到的公共钥匙串,使页面的主体变回原状,表明我已成功提出请求,我更改了钥匙的一个字母以确保它可以正常工作,并且我知道自己已步入正轨.

Then replacing the public keychains I got in the error yielded back the page's body, indicating I had made a successful request, I change one letter of the key to make sure it was working and I knew I was on track.

我终于在ToastModule.java文件中有了这个方法

I finally had this method in my ToastModule.java file

@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请求很好,现在我可以使用我创建的方法,但是理想情况下,我认为最好扩展现有客户端,以便立即获得实现的好处.

Android Solution Extending React Native's OkHttpClient

Having figured out how to send pinned http request was good, now I can use the method I created, but ideally I thought it would be best to extend the existing client, so as to immediately gain the benefit of implementing.

此解决方案自RN0.35起有效,并且我不知道将来如何解决.

This solution is valid as of RN0.35 and I don't know how it will fair in the future.

在研究为RN扩展OkHttpClient的方式时,我遇到了

While looking into ways of extending the OkHttpClient for RN I came across this article explaining how to add TLS 1.2 support through replacing the SSLSocketFactory.

读到它,我了解到React使用OkHttpClientProvider创建XMLHttpRequest对象使用的OkHttpClient实例,因此,如果我们替换该实例,则会将钉扎应用于所有应用程序.

reading it I learned react uses an OkHttpClientProvider for creating the OkHttpClient instance used by the XMLHttpRequest Object and therefore if we replace that instance we would apply pinning to all the app.

我在android/app/src/main/java/com/dreidev文件夹中添加了一个名为OkHttpCertPin.java的文件

I added a file called OkHttpCertPin.java to my android/app/src/main/java/com/dreidev folder

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对其进行重建,并返回新建的实例.

This package has a method extend which takes an existing OkHttpClient and rebuilds it adding the certificatePinner and returns the newly built instance.

然后我按照此答案的建议修改了我的MainActivity.java文件,方法是添加以下方法

I then modified my MainActivity.java file following this answer's advice by adding the following methods

.
.
.
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方法,因为检查提供程序时,我意识到

This solution was carried out in favor of completely reimplementing the OkHttpClientProvider createClient method, as inspecting the provider I realized that the master version had implemented TLS 1.2 support but was not yet an available option for me to use, and so rebuilding was found to be the best means of extending the client. I'm wondering how this approach will fair as I upgrade but for now it works well.

更新:从0.43开始,此技巧似乎不再起作用.出于时间限制,我暂时将项目冻结在0.42,直到明确了重建停止工作的原因为止.

Update It seems that starting 0.43 this trick no longer works. For timebound reasons I will freeze my project at 0.42 for now, until the reason for why rebuilding stopped working is clear.

对于IOS,我曾以为我需要遵循一种类似的方法,再次以工藤的提议为主导.

For IOS I had thought I would need to follow a similar method, again starting with Kudo's proposal as my lead.

检查RCTNetwork模块后,我了解到使用了NSURLConnection,因此我没有尝试按照提案中的建议使用AFNetworking创建一个全新的模块,而是发现了

Inspecting the RCTNetwork module I learned that NSURLConnection was used, so instead of trying to create a completely new module with AFNetworking as suggested in the proposal I discovered TrustKit

按照我刚刚添加的《入门指南》

following its Getting Started Guide I simply added

pod 'TrustKit'

到我的podfile并运行pod install

to my podfile and ran pod install

GettingStartedGuide解释了如何从pList.file配置此Pod,但比起配置文件更喜欢使用代码,我在AppDelegate.m文件中添加了以下几行

the GettingStartedGuide explained how I can configure this pod from my pList.file but preferring to use code than configuration files I added the following lines to my AppDelegate.m file

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

我从android系统实现中获得了公钥散列,并且该散列起作用了(我在pod中收到的TrustKit版本是1.3.2)

I got the public key hashes from my android implementation and it just worked (the version of TrustKit I received in my pods is 1.3.2)

我很高兴iOS竟然是一口气

I was glad IOS turned out to be a breath

作为一个旁注,TrustKit警告说,如果NSURLSession和Connection已经混乱,它的自动混乱将无法工作.那说到目前为止似乎运作良好.

As a side note TrustKit warned that it's Auto-swizzle won't work if the NSURLSession and Connection are already swizzled. that said it seems to be working well so far.

结论

鉴于我能够用本机代码实现此解决方案,因此此答案提供了适用于Android和IOS的解决方案.

Conclusion

This answer presents the solution for both Android and IOS, given I was able to implement this in native code.

一项可能的改进可能是实现一个通用平台模块,在该模块中可以使用javascript管理设置公共密钥以及配置android和IOS的网络提供程序.

One possible improvement may be to implement a common platform module where setting public keys and configuring the Network providers of both android and IOS can be managed in javascript.

工藤的提议提到的只是将公钥添加到js包中但是,该漏洞可能会暴露一个漏洞,在该漏洞中可以以某种方式替换捆绑文件.

Kudo's proposal mentioned simply adding the public keys to the js bundle may however expose a vulnerability, where somehow the bundle file can be replaced.

我不知道该攻击媒介如何发挥作用,但可以肯定的是,按照建议的方法对bundle.js进行签名可能会保护js捆绑包.

I don't know how that attack vector can function, but certainly the extra step of signing the bundle.js as proposed may protect the js bundle.

另一种方法可能是将js包简单地编码为64位字符串,然后直接将其包含在本机代码中,作为

Another approach may be to simply encode the js bundle into a 64 bit string and include it in the native code directly as mentioned in this issue's conversation. This approach has the benefit of obfuscating as well hardwiring the js bundle into the app, making it inaccessible for attackers or so I think.

如果您读了这么多书,希望对您的bug修复工作有所启发,并祝您阳光明媚.

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

这篇关于使用React Native时如何实现SSL证书固定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆