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

我对SSL/TLS知之甚少,更不用说钉扎了.

我开始寻找如何执行这项任务.

React Native是否已经实现了这一点?

不,我最初的搜索结果是this proposal,自2016年8月2日以来没有收到任何活动.

从中我了解到react native使用OkHttp,它确实支持固定,但我无法从Javascript中实现它,这不是真正的需求,而是一个优点.

用Javascript实现它.

虽然react似乎使用了nodejs运行时,但它更像是一个浏览器而不是 node ,这意味着它不支持所有本机模块,特别是https模块,我已经为它实现了证书固定.因此不能把它带进我的房间.

我试着使用rn nodeify,但模块不起作用.从RN 0.33到RN 0.35,这就是我目前的情况.

使用phonegap插件实现

我曾想过使用phongape-plugin,但因为我依赖于需要react 0.32+的库,所以我不能使用react-native-cordova-plugin

Just do it natively

虽然我不是本地应用程序开发人员,但我总能try 一下,这只是时间问题.

Android有证书固定功能

我了解到android supports SSL Pinning不成功,因为在android 7之前,这种方法似乎不起作用.而且只为安卓系统工作.

底线

我已经穷尽了几个方向,并将继续追求更多的本机实现,可能会找到如何配置OkHttp和RNNetworking,然后可能会桥接回react native.

但是,IOS和安卓系统已经有了任何实现或指南吗?

推荐答案

在从Javascript中用尽了当前可用的选项之后,我决定简单地实现本机的证书固定,这一切看起来都很简单,现在我已经完成了.

如果不想通读获取解决方案的过程,请跳到标题为Android SolutionIOS Solution的标题.

Android

Kudo's recommendation之后,我想到了使用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();

我首先学习了如何创建一个本地模块,创建一个toast模块.然后,我用一种发送简单请求的方法对其进行了扩展

@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;

Kudo的方法不清楚我从哪里获得公钥,也不清楚如何生成公钥.幸运的是,okhttp3 docs除了提供如何使用证书的清晰演示外,Pinner还表示,要获得公钥,我需要做的就是使用错误的pin发送请求,正确的pin将出现在错误消息中.

花了一点时间才意识到.Builder()可以链接,我可以在构建之前包含CertificatePaner,这与我提出的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());
    }
}

然后,替换错误中的公钥链,返回页面正文,表明我已成功提出请求,我更改了密钥的一个字母,以确保它正常工作,并且我知道我已经走上正轨.

我终于在我的演讲模块中使用了这种方法.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年前开始有效,我不知道将来它会如何公平.

在研究扩展OkHttpClient for RN的方法时,我遇到了this article个例子,解释了如何通过替换SSLSocketFactory来添加TLS 1.2支持.

阅读本文后,我了解到react使用OkHttpClientProvider创建XMLHttpRequest对象使用的OkHttpClient实例,因此,如果替换该实例,我们将对所有应用程序应用钉住.

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

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

这个包有一个方法extend,它接受现有的OkHttpClient并通过添加CertificatePaner来重建它,然后返回新构建的实例.

然后我修改了我的主要活动.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方法,在判断提供程序时,我意识到the master version已经实现了TLS 1.2支持,但还不是我可以使用的选项,因此发现重建是扩展客户端的最佳方法.我想知道在升级时这种方法如何公平,但目前它运行良好.

开始这个技巧似乎不再有效.出于时间限制的原因,我现在将我的项目冻结在0.42,直到重建停止工作的原因明确为止.

Solution IOS

对于IOS,我曾认为我需要遵循类似的方法,再次从工藤的提议开始.

在判断RCTNetwork模块时,我了解到使用了NSURLConnection,因此我没有像提案中建议的那样try 使用AFNetworking创建一个全新的模块,而是发现了TrustKit

在《入门指南》之后,我简单地添加了

pod 'TrustKit'

到我的播客文件,跑了pod install

GettingStartedGuide解释了如何从pList配置这个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,
           kTSKPublicKey算法rithms : @[kTSK算法rithmRsa2048],

           // 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实现中获得了公钥散列,它刚刚起作用(我在播客中收到的TrustKit版本是1.3.2)

我很高兴IOS被证明是一种呼吸

作为旁注,TrustKit警告说,如果NSURLSession和连接已被swizzle,它的自动swizzle将无法工作.也就是说,到目前为止,它似乎运行良好.

Conclusion

这个答案为Android和IOS提供了解决方案,因为我能够用本机代码实现这一点.

一个可能的改进可能是实现一个通用平台模块,在该模块中,可以用javascript管理安卓和IOS的公钥设置和网络Provider 配置.

不过,简单地将公钥添加到jsBundle 包可能会expose 漏洞,以某种方式可以替换Bundle 包文件.

我不知道攻击向量是如何运作的,但肯定是额外的步骤.提议的js可能会保护js包.

另一种方法可能是简单地将js包编码为64位字符串,并将其作为mentioned in this issue's conversation直接包含在本机代码中.这种方法的好处是混淆js包,并将其硬连接到应用程序中,使攻击者无法访问它,我认为是这样.

如果你读到这篇文章,我希望我能启发你修复bug的方法,并祝你度过一个阳光灿烂的日子.

Objective-c相关问答推荐

反编译ARM64和理解分支目标边界

日期更改时的 UIDatePicker

TWTweetComposeViewController 在 IOS6 中已弃用

如何创建圆形按钮?

Objective-C 对象的列表 Select 器

Objective-C:在字符串中查找数字

Xcode 中架构的重复符号

当前时间是 HH:MM:SS am/pm 格式吗?

将 CSS 插入 UIWebView / WKWebView 中加载的 HTML

Facebook 添加测试用户和管理员是:(待定),这是什么意思?

iOS 中 textField 上的Electron邮件验证

iPhone - 时区便利方法之间的差异

具有多个嵌入 segues 的 ContainerView

自定义 UINavigationBar 背景

使用 OpenCV 在 iOS 中进行透视变换 + 裁剪

发送通知到山狮通知中心

当 UIView 框架更改时,视图内的 AVPlayer 层不会调整大小

Instruments Allocations 跟踪用户定义类的对象的分配和解除分配

使 UILabel 的文本加粗

@package 指令在 Objective-C 中的作用是什么?