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

Amr Draz:

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

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

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

React Native尚未实现吗?

不,我的初步搜索使我想到了这个提案,自2016年8月2日以来没有任何活动。

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

用Java脚本实现。

虽然react似乎使用了nodejs运行时,但它更像是一个浏览器而不是node,这意味着它不支持所有本机模块,特别是https模块,本文在本文中实现了证书固定因此无法将其带入本地反应。

我尝试使用rn-nodeify,但是模块不起作用。自从我目前在RN 0.33到RN 0.35以来,这是正确的。

使用phonegap插件实施

我考虑过使用phongape插件,但是由于我对需要react 0.32+的库有依赖性,所以我不能使用react-native-cordova-plugin

只是本地做

虽然我不是本地应用程序开发人员,但我总是可以尽力解决问题,只是时间问题。

Android具有证书固定

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

底线

我已经用尽了几个方向,将继续追求更多的本机实现,也许想出如何配置OkHttp和RNNetworking,然后再桥接回本机。

但是,已经有针对iOS和android的实现或指南了吗?

Amr Draz:

在用尽了Javascript当前可用选项的全部范围之后,我决定只简单地本地实现证书固定,现在一切都变得如此简单。

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

安卓系统

遵循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();

我首先开始学习如何通过响应本机创建Toast模块来创建本机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的清晰演示之外,还指出要获取公钥,我所要做的就是发送带有不正确引脚的请求,并且正确的引脚将出现在错误消息中。

花了一点时间意识到可以链接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());
    }
}

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

我终于在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支持。

阅读它,我了解到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

对于IOS,我曾以为我需要遵循一种类似的方法,同样从Kudo的建议开始。

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

遵循我的入门指南,我只是添加了

pod 'TrustKit'

到我的podfile并运行 pod install

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 [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实现中获得了公钥散列,并且它可以正常工作(我在pod中收到的TrustKit版本是1.3.2)

我很高兴IOS真是令人屏息

附带说明一下,TrustKit警告说,如果NSURLSession和Connection已经混乱,它的Auto-swizzle将不起作用。那说到目前为止似乎运作良好。

结论

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

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

Kudo的建议提到,仅将公共密钥添加到js捆绑包中可能会暴露一个漏洞,在该漏洞中可以以某种方式替换捆绑包文件。

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

另一种方法可能是将js捆绑包简单地编码为64位字符串,然后将其直接包含在本机代码中,如本期话题所述这种方法的好处是可以将js捆绑包混淆并硬接线到应用程序中,从而使攻击者无法访问它。

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何配置axios以使用SSL证书?

如何使用Square OKHTTP固定证书?

使用TrustKit iOS在react-native应用程序中实现ssl固定

使用.start_tls_s()时如何强制Python LDAP验证/验证SSL证书

SSL证书错误:在我的Laravel网站上使用Twilio时,证书链中的自签名证书

在Java中使用自签名证书时SSL如何工作

使用Nancy时如何将SSL证书传递给Nowin

使用libcurl固定SSL证书

如何对Amazon ELB SSL使用* .pfx证书

如何使用React Native在Android上实现CallKit类似行为?

如何使用gl-react-native实现此效果?

使用React Native Web时如何将道具传递给react-native?

使用React-Navigation和Redux的React Native-如何实现全局可用的“注销”按钮

如何使用C#验证SSL证书

在React Native中实现Geojson时出错

使用React Native Web时如何选择文件?

使用React的Promises时的实现问题

使用Redux时如何重新呈现FlatList React Native

使用不同的ISP访问同一资源时如何接收不同的SSL证书?

如何识别SSL中使用的证书的PKCS?

使用代理拦截SSL流量的Web保护产品如何与实现SSL固定的网站一起使用?

React Native:如何实现DatePickerAndroid?

如何使用OkHttp启用证书固定

在使用react-redux实现react-native时出错

如何使用 Kubernetes SSL 证书

如何在映射 React Native 时使用 setState

如何使用 React Native 实现应用内浏览器?

如何使用 QWebEngineView 忽略 SSL 证书错误

仅使用前端时如何获取 HTTPS SSL 证书