草庐IT

android - 如何在使用 React Native 时实现 SSL 证书锁定

coder 2023-06-05 原文

我需要在我的 native 应用程序中实现 SSL 证书锁定。

我对 SSL/TLS 知之甚少,更不用说固定了。
我也不是本地移动开发人员,尽管我了解 Java 并在这个项目中学习了足够多的 Objective-C。

我开始寻找如何执行这个任务。

React Native 不是已经实现了吗?

不,我最初的搜索将我带到 this proposal自 2016 年 8 月 2 日以来没有收到任何 Activity 。

从它我了解到,react-native 使用支持 Pinning 的 OkHttp,但我无法将它从 Javascript 中拉出来,这不是真正的要求,而是一个加分项。

在 Javascript 中实现它。

虽然 react 看起来像是使用 nodejs 运行时,但它更像是一个浏览器而不是 node,这意味着它不支持所有原生模块,特别是 https 模块,我在 this article 之后为它实现了证书锁定。 .因此无法将其带入 react 原生。

我尝试使用 rn-nodeify 但模块不起作用。从我目前使用的 RN 0.33 到 RN 0.35 以来,情况一直如此。

使用 phonegap 插件实现

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

就在本地做

虽然我不是本地应用程序开发人员,但我总是可以尝试一下,只是时间问题。

Android 有证书固定

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

底线

我已经用尽了几个方向,并将继续追求更多的原生实现,也许弄清楚如何配置 OkHttp 和 RNNetworking 然后可能会桥接回 react-native。

但是是否已经有适用于 IOS 和 android 的实现或指南?

最佳答案

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

Skip to headers titled Android Solution and IOS Solution if you don't want to read through the process of reaching the solution.



安卓

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

我首先学习如何创建一个原生的 android bridge with react native 创建一个 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 除了提供了如何使用 CertificatePinner 的清晰演示之外,还指出要获取公钥,我需要做的就是发送一个带有错误 pin 的请求,正确的 pin 将出现在错误消息中。

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

然后替换我在错误中得到的公共(public)钥匙串(keychain),返回页面的正文,表明我已成功提出请求,我更改了 key 的一个字母以确保它正常工作,并且我知道我正在走上正轨。

我终于在我的 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());
    }
}

扩展 React Native 的 OkHttpClient 的 Android 解决方案

弄清楚如何发送固定的 http 请求很好,现在我可以使用我创建的方法,但理想情况下我认为最好扩展现有客户端,以便立即获得实现的好处。

此解决方案自 RN0.35 起有效,我不知道将来会如何公平。

在研究为 RN 扩展 OkHttpClient 的方法时,我遇到了 this article,解释了如何通过替换 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;
   }
}

这个包有一个方法 extend ,它接受一个现有的 OkHttpClient 并重建它,添加 certificatePinner 并返回新构建的实例。

然后我在 this answer's advice 之后通过添加以下方法修改了我的 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 方法,在检查提供程序时,我意识到 the master version 已经实现了 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 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)

我很高兴IOS竟然是一口气

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.



结论

鉴于我能够在 native 代码中实现这一点,此答案提供了适用于 Android 和 IOS 的解决方案。

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

Kudo's proposal 提到简单地将公钥添加到 js 包中可能会暴露一个漏洞,其中可以以某种方式替换包文件。

我不知道该攻击向量如何发挥作用,但当然,按照提议对 bundle.js 进行签名的额外步骤可能会保护 js 包。

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

如果您读到这里,我希望我能启发您修复错误,并希望您享受阳光明媚的一天。

关于android - 如何在使用 React Native 时实现 SSL 证书锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40240321/

有关android - 如何在使用 React Native 时实现 SSL 证书锁定的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐