简体   繁体   English

如何在使用React Native时实现SSL证书固定

[英]How can I implement SSL Certificate Pinning while using React Native

I need to implement SSL Certificate Pinning in my react native application. 我需要在我的反应本机应用程序中实现SSL证书固定。

I know very little about SSL/TLS let alone pinning. 我对SSL / TLS知之甚少,更不用说固定了。 I am also not a native mobile developer, though I know Java and learned Objective-C on this project enough to get around. 我也不是一个本地移动开发人员,虽然我了解Java并且在这个项目上学习了Objective-C足以绕过它。

I started searching for how to execute this task. 我开始搜索如何执行此任务。

Doesn't React Native already implement this? React Native没有实现这个吗?

No, My initial search lead me to this proposal which has received no activity since August 2nd 2016. 不,我的初步搜索引导我查看自2016年8月2日以来未收到任何活动的提案

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. 从中我了解到react-native使用的OkHttp确实支持Pinning,但是我无法将其从Javascript中删除,这不是真正的要求,而是一个加号。

Implement it in Javascript. 在Javascript中实现它。

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 . 虽然反应似乎使用了nodejs运行时,但它更像是一个浏览器而不是节点,这意味着它不支持所有本机模块,特别是https模块,我已经在本文后面实现了证书固定。 Thus could not carry it into react native. 因此无法将其带入本机反应。

I tried using rn-nodeify but the modules didn't work. 我尝试使用rn-nodeify,但模块不起作用。 This has been true since RN 0.33 to RN 0.35 which I'm currently on. 自从我目前正在使用RN 0.33到RN 0.35以来,这是真的。

Implement using phonegap plugin 使用phonegap插件实现

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 我想过使用phongape-plugin然而因为我依赖于需要反应0.32+的库我不能使用react-native-cordova-plugin

Just do it natively 只是本地做

While I'm not a native app developer I can always take a crack at it, only a matter of time. 虽然我不是本机应用程序开发人员,但我总是可以解决它,只是时间问题。

Android has certificate pinning 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. 我了解到android支持SSL Pinning但是不成功,因为看起来这种方法在Android 7之前不起作用。以及仅适用于Android。

The bottom line 底线

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. 我已经用尽了几个方向,并将继续寻求更多本机实现,也许可以弄清楚如何配置OkHttp和RNNetworking然后可能会回到本地反应。

But is there already any implementations or guide for IOS and android? 但是,IOS和android已经有任何实现或指南吗?

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. 在从Javascript中耗尽了当前频谱的可用选项后,我决定简单地实现本地证书固定,现在看来我已经完成了所有这些。

Skip to headers titled Android Solution and IOS Solution if you don't want to read through the process of reaching the solution. 如果您不想阅读解决方案的过程,请跳至标题为Android SolutionIOS Solution的标题。

Android Android的

Following Kudo's recommendation I thought out to implement pinning using okhttp3. 按照Kudo的建议,我考虑使用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();

I first started by learning how to create a native android bridge with react native creating a toast module. 我首先学习如何使用react native创建一个toast模块创建一个本机android桥 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;

Kudo's approach wasn't clear on where I would get the public keys or how to generate them. Kudo的方法并不清楚我将获得公钥或如何生成公钥。 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. 幸运的是, okhttp3文档除了提供如何使用CertificatePinner的明确演示之外,还说明要获取公钥,我需要做的就是发送一个带有错误引脚的请求,并且错误信息中会出现正确的引脚。

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. 花了一点时间才意识到OkHttpClent.Builder()可以被链接,我可以在构建之前包含CertificatePinner,不像Kudo提案中的误导性示例(可能和旧版本),我提出了这种方法。

@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. 然后更换我在错误中得到的公共钥匙链产生了页面的正文,表明我已经成功请求,我更改了密钥的一个字母,以确保它正常工作,我知道我正在进行中。

I finally had this method in my ToastModule.java file 我终于在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 Solution Extending React Native's OkHttpClient Android解决方案扩展了React Native的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. 已经弄清楚如何发送固定的http请求是好的,现在我可以使用我创建的方法,但理想情况下我认为最好扩展现有的客户端,以便立即获得实现的好处。

This solution is valid as of RN0.35 and I don't know how it will fair in the future. 该解决方案自RN0.35有效,我不知道将来如何公平。

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. 在研究扩展OkHttpClient for RN的方法时,我遇到了这篇文章,解释了如何通过替换SSLSocketFactory来添加TLS 1.2支持。

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. 阅读它我学会了反应使用OkHttpClientProvider来创建XMLHttpRequest对象使用的OkHttpClient实例,因此如果我们替换该实例,我们会将pinning应用于所有应用程序。

I added a file called OkHttpCertPin.java to my android/app/src/main/java/com/dreidev folder 我添加了一个名为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;
   }
}

This package has a method extend which takes an existing OkHttpClient and rebuilds it adding the certificatePinner and returns the newly built instance. 这个包有一个方法扩展,它接受现有的OkHttpClient并重建它添加certificatePinner并返回新构建的实例。

I then modified my MainActivity.java file following this answer's advice by adding the following methods 然后我按照这个答案的建议修改了我的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);
  }
.
.
.

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. 执行此解决方案有利于完全重新实现OkHttpClientProvider createClient方法,因为检查提供程序我意识到主版本已实现了TLS 1.2支持,但还不是我可以使用的选项,因此重建被发现是扩展客户的最佳方式。 I'm wondering how this approach will fair as I upgrade but for now it works well. 我想知道这种方法在升级时会如何公平,但现在效果很好。

Update It seems that starting 0.43 this trick no longer works. 更新似乎从0.43开始这个技巧不再有效。 For timebound reasons I will freeze my project at 0.42 for now, until the reason for why rebuilding stopped working is clear. 由于时间原因,我现在将我的项目冻结在0.42,直到为什么重建停止工作的原因很清楚。

Solution IOS 解决方案IOS

For IOS I had thought I would need to follow a similar method, again starting with Kudo's proposal as my lead. 对于IOS,我原本以为我需要遵循类似的方法,再次以Kudo的提议开始作为我的领导。

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 检查RCTNetwork模块我了解到使用了NSURLConnection,因此我没有尝试使用AFNetworking创建一个全新的模块,如我所提到的那样,我发现了TrustKit

following its Getting Started Guide I simply added 按照其入门指南我简单地添加

pod 'TrustKit'

to my podfile and ran pod install 到我的podfile并运行pod install

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 GettingStartedGuide解释了我如何从我的pList.file配置这个pod,但更喜欢使用代码而不是配置文件我将以下行添加到我的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 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];
.
.
.

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) 我从我的android实现中得到了公钥哈希,它刚刚起作用(我在我的pod中收到的TrustKit的版本是1.3.2)

I was glad IOS turned out to be a breath 我很高兴IOS原来是一口气

As a side note TrustKit warned that it's Auto-swizzle won't work if the NSURLSession and Connection are already swizzled. 作为旁注,TrustKit警告说,如果NSURLSession和Connection已经被淘汰,那么Auto-swizzle将不起作用。 that said it seems to be working well so far. 这说它到目前为止似乎运作良好。

Conclusion 结论

This answer presents the solution for both Android and IOS, given I was able to implement this in native code. 这个答案为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. 一种可能的改进可以是实现公共平台模块,其中可以在javascript中管理设置公钥和配置android和IOS的网络提供者。

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. Kudo的提议提到简单地将公钥添加到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. 我不知道攻击向量是如何起作用的,但是肯定是按照建议签署bundle.js的额外步骤可以保护js包。

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 . 另一种方法可能是简单地将js包编码为64位字符串,并将其直接包含在本机代码中,如本期对话中所述 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. 这种方法的好处是可以将js捆绑软件连接到应用程序中,使得攻击者无法访问它,所以我认为。

If you read this far I hope I enlightened you on your quest for fixing your bug and wish you enjoy a sunny day. 如果你读到这里,我希望我能帮助你修复你的虫子并希望你享受阳光灿烂的日子。

You can use this lib https://github.com/nlt2390/react-native-pinning-ssl 您可以使用此库https://github.com/nlt2390/react-native-pinning-ssl

It verifies SSL connection using SHA1 keys, not certificates. 它使用SHA1密钥验证SSL连接,而不是证书。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM